﻿{                                                                           }
{ File:       Decimals.pas                                                  }
{ Function:   Decimal data type, an emulation of the type of the same name  }
{             in .NET. It is a floating decimal point binary mantissa data  }
{             type completely calculated in software, so it is slow, but    }
{             well suited for calculations that require accuracy,           }
{             especially financial ones.                                    }
{ Language:   Delphi 2009 and above (routines assume Char = WideChar)       }
{ Author:     Rudy Velthuis                                                 }
{ Copyright:  (c) 2010 Rudy Velthuis                                        }
{ Version:    2.5                                                           }
{ References: Donald Knuth, The Art of Computer Programming, Vol. 2:        }
{             Seminumerical Algorithms                                      }
{ Notes:      I am working on an update that will also work in versions     }
{             below Delphi 2009.                                            }
{                                                                           }
{             Function ToString(Format) not functional yet.                 }
{             Function ToString() works fine.                               }
{                                                                           }
{ Disclaimer: This code is freeware. All rights are reserved.               }
{             This code is provided as is, expressly without a warranty     }
{             of any kind. You use it at your own risk.                     }
{                                                                           }
{             If you use this code, please credit me.                       }
{                                                                           }


{ $DEFINE PUREPASCAL}
{ $DEFINE MULTCONST}

unit KRK.Lib.Rtl.Common.Decimals;

{$WEAKPACKAGEUNIT ON}

interface

uses
  SysUtils, Math;

type
  TLongwordArray = array of Longword;

  TFormatRec = packed record
    Exponent: Smallint;
    Negative: Boolean;
    Digits: array[0..60] of WideChar;
  end;

  Decimal = packed record
  public
    class var MinValue: Decimal;
    class var MaxValue: Decimal;
    class var One: Decimal;
    class var Zero: Decimal;
    class var MinusOne: Decimal;
    class var OneTenth: Decimal;
    class var Pi: Decimal;
  public
    constructor Create(From: array of Longword); overload;
    constructor Create(From: Currency); overload;
    constructor Create(From: Extended); overload;
    constructor Create(From: Int64); overload;
    constructor Create(From: Longint); overload;
    constructor Create(From: Longword); overload;
    constructor Create(From: UInt64); overload;
    constructor Create(Lo, Mid, Hi: Longword; IsNegative: Boolean; scale: Byte); overload;

    function CompareTo(const D: Decimal): TValueSign;
    function Equals(const D: Decimal): Boolean; overload;
    function GetBytes: TBytes;
    function IsNegative: Boolean; overload;
    function IsZero: Boolean; overload;
    function ToCurrency: Currency;
    function ToDouble: Double;
    function ToExtended: Extended;
    function ToInt64: Int64;
    function ToLongint: Longint;
    function ToLongword: Longword;
    function ToSingle: Single;
    function ToString(const Settings: TFormatSettings): string; overload;
    function ToString(Format: string): string; overload;
    function ToString(Format: string; const Settings: TFormatSettings): string; overload;
    function ToString: string; overload;
    function ToUInt64: UInt64;

    class function Abs(const D: Decimal): Decimal; static;
    class function Add(const Left, Right: Decimal): Decimal; static;
    class function Ceiling(const D: Decimal): Decimal; static;
    class function Compare(const Left, Right: Decimal): TValueSign; static;
    class function Divide(const Left, Right: Decimal): Decimal; static;
    class function Equals(const Left, Right: Decimal): Boolean; overload; static;
    class function Floor(const D: Decimal): Decimal; static;
    class function Frac(const D: Decimal): Decimal; static;
    class function GetBits(const D: Decimal): TLongwordArray; static;
    class function Multiply(const Left, Right: Decimal): Decimal; static;
    class function Negate(const D: Decimal): Decimal; static;
    class function Parse(const S: string): Decimal; overload; static;
    class function Parse(const S: string; Settings: TFormatSettings): Decimal; overload; static;
    class function PowerOfTen(Power: Shortint): Decimal; static;
    class function Reciprocal(const D: Decimal): Decimal; static;
    class function Remainder(const Left, Right: Decimal): Decimal; static;
    class function Round(const D: Decimal): Decimal; static;
    class function RoundTo(const D: Decimal; Digits: Integer): Decimal; static;
    class function Sqr(const D: Decimal): Decimal; static;
    class function Subtract(const Left, Right: Decimal): Decimal; static;
    class function Truncate(const D: Decimal): Decimal; static;
    class function TryParse(const S: string; const Settings: TFormatSettings; out Res: Decimal): Boolean; overload; static;
    class function TryParse(const S: string; out Res: Decimal): Boolean; overload; static;

    class operator Add(const Left, Right: Decimal): Decimal;
    class operator Dec(const D: Decimal): Decimal;
    class operator Divide(const Left, Right: Decimal): Decimal;
    class operator Equal(const Left, Right: Decimal): Boolean;
    class operator GreaterThan(const Left, Right: Decimal): Boolean;
    class operator GreaterThanOrEqual(const Left, Right: Decimal): Boolean;
    class operator Implicit(const C: Cardinal): Decimal;
    class operator Implicit(const c: Currency): Decimal;
    class operator Implicit(const D: Decimal): Currency;
    class operator Implicit(const D: Decimal): Extended;
    class operator Implicit(const D: Decimal): Int64;
    class operator Implicit(const D: Decimal): Longint;
    class operator Implicit(const D: Decimal): Longword;
    class operator Implicit(const D: Decimal): string;
    class operator Implicit(const D: Decimal): UInt64;
    class operator Implicit(const D: Double): Decimal;
    class operator Implicit(const E: Extended): Decimal;
    class operator Implicit(const I: Integer): Decimal;
    class operator Implicit(const I64: Int64): Decimal;
    class operator Implicit(const S: Single): Decimal;
    class operator Implicit(const S: string): Decimal;
    class operator Implicit(const UI64: UInt64): Decimal;
    class operator Inc(const D: Decimal): Decimal;
    class operator LessThan(const Left, Right: Decimal): Boolean;
    class operator LessThanOrEqual(const Left, Right: Decimal): Boolean;
    class operator Modulus(const Dividend, Divisor: Decimal): Decimal;
    class operator Multiply(const Left, Right: Decimal): Decimal;
    class operator Negative(const D: Decimal): Decimal;
    class operator NotEqual(const Left, Right: Decimal): Boolean;
    class operator Positive(const D: Decimal): Decimal;
    class operator Round(const D: Decimal): Decimal;
    class operator Subtract(const Left, Right: Decimal): Decimal;
    class operator Trunc(const D: Decimal): Decimal;
{$IFNDEF DEBUG}
  private
{$ENDIF}
{$IF CompilerVersion >= 21.0}
    class constructor CreateRecord;
{$ENDIF}
    class function Div192by32(Quotient: PLongword; const Dividend: Decimal; Divisor: Longword): Longword; static;
    class function Div192by64(Quotient: PLongword; const Dividend, Divisor: Decimal): Longword; static;
    class function Div192by96(Quotient: PLongword; const Dividend, Divisor: Decimal): Longword; static;
    class function Mod96by5(const D: Decimal): Integer; static;
    class procedure InternalAdd(var Result: Decimal; const Left, Right: Decimal); static;
    class procedure InternalAddSub(var Result: Decimal; const Left, Right: Decimal; Sign: Byte); static;
    class procedure InternalCeiling(var Result: Decimal; const Source: Decimal); static;
    class procedure InternalDivide(var Result: Decimal; const Left, Right: Decimal); static;
    class procedure InternalFloor(var Result: Decimal; const Source: Decimal); static;
    class procedure InternalFromDouble(out Result: Decimal; const Source: Double); static;
    class procedure InternalFromExtended(out Result: Decimal; const Source: Extended); static;
    class procedure InternalFromSingle(out Result: Decimal; const Source: Single); static;
    class procedure InternalMultiply(var Result: Decimal; const Left, Right: Decimal); static;
    class procedure InternalRound(var Result: Decimal; const Source: Decimal); static;
    class procedure InternalSubtract(var Result: Decimal; const Left, Right: Decimal); static;
    class procedure InternalToDouble(var Result: Double; const Source: Decimal); static;
    class procedure InternalToExtended(var Result: Extended; const Source: Decimal); static;
    class procedure InternalToSingle(var Result: Single; const Source: Decimal); static;
    class procedure InternalTruncate(var Result: Decimal; const Source: Decimal); static;
  private
    Lo: Longword;               // Hi:Mid:Lo form 96 bit unsigned mantissa
    Mid: Longword;
    Hi: Longword;
    case Byte of
      0: (Reserved: Word;       // always 0
          Scale: Shortint;      // 0..28
          Sign: Byte);          // $80 = negative, $00 = positive
      1: (Flags: Longword);
  end;

function StrToDecimal(const S: string): Decimal;
function DecimalToStr(const D: Decimal): string;

var
  DoDump: Boolean = False;

implementation

{$POINTERMATH ON}

uses
  Windows;

resourcestring
  SDecimalOverflow     = 'Decimal overflow';
  SDecimalUnderflow    = 'Decimal underflow';
  SDecimalZeroDivide   = 'Decimal division by zero';
  SDecimalInvalidOp    = 'Invalid decimal operation';
  SErrorDecimalParsing = '''%S'' is not a valid decimal value';
  SConversionFailed    = 'Decimal value too large for conversion to %S';
  SInvalidArg          = 'Invalid argument: %S';
  SNan                 = 'Invalid argument: NaN (not a number)';
  SEmptyString         = 'empty string';

const
  SingleMShift   = 8;
  SingleMMask    = $007FFFFF;
  SingleEShift   = 23;
  SingleEMask    = $FF;
  SingleBias     = $7F;

  DoubleMShift   = 11;
  DoubleMMask    = $000FFFFF;
  DoubleEShift   = 52;
  DoubleEMask    = $7FF;
  DoubleBias     = $3FF;

  ExtendedEShift = 64;
  ExtendedEMask  = $7FFF;
  ExtendedBias   = $3FFF;

  CMaxValue: Decimal = (Lo: $FFFFFFFF; Mid: $FFFFFFFF; Hi: $FFFFFFFF; Scale:  0; Sign: $00);
  CMinValue: Decimal = (Lo: $FFFFFFFF; Mid: $FFFFFFFF; Hi: $FFFFFFFF; Scale:  0; Sign: $80);
  CMinusOne: Decimal = (Lo: $00000001; Mid: $00000000; Hi: $00000000; Scale:  0; Sign: $80);
  CZero: Decimal     = (Lo: $00000000; Mid: $00000000; Hi: $00000000; Scale:  0; Sign: $00);
  COne: Decimal      = (Lo: $00000001; Mid: $00000000; Hi: $00000000; Scale:  0; Sign: $00);
  COneTenth: Decimal = (Lo: $00000001; Mid: $00000000; Hi: $00000000; Scale:  1; Sign: $00);
  CPi: Decimal       = (Lo: $41B65F29; Mid: $0B143885; Hi: $6582A536; Scale: 28; Sign: $00);

  HLW = High(Longword);
  HUI64 = High(UInt64);
  HLWdiv10 = HLW div 10;
  HUI64div10 = Longword(HUI64 div 10);

  PowersOfTen: array[0..9] of Longword =
  (
             1,
            10,
           100,
          1000,
         10000,
        100000,
       1000000,
      10000000,
     100000000,
    1000000000
  );

  MaxMultiplicands: array[0..9] of Longword =
  (
    HLW,
    HLW div 10,
    HLW div 100,
    HLW div 1000,
    HLW div 10000,
    HLW div 100000,
    HLW div 1000000,
    HLW div 10000000,
    HLW div 100000000,
    HLW div 1000000000
  );

  MaxMultiplicandsMid: array[0..9] of Longword =
  (
    Longword(HUI64),
    Longword(HUI64 div 10),
    Longword(HUI64 div 100),
    Longword(HUI64 div 1000),
    Longword(HUI64 div 10000),
    Longword(HUI64 div 100000),
    Longword(HUI64 div 1000000),
    Longword(HUI64 div 10000000),
    Longword(HUI64 div 100000000),
    Longword(HUI64 div 1000000000)
  );

  DecimalPowersOfTen: array[-28..28] of Decimal =
  (
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $001C0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $001B0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $001A0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00190000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00180000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00170000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00160000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00150000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00140000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00130000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00120000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00110000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00100000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $000F0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $000E0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $000D0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $000C0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $000B0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $000A0000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00090000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00080000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00070000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00060000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00050000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00040000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00030000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00020000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00010000),
    (Lo: $00000001; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $0000000A; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $00000064; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $000003E8; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $00002710; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $000186A0; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $000F4240; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $00989680; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $05F5E100; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $3B9ACA00; Mid: $00000000; Hi: $00000000; Flags: $00000000),
    (Lo: $540BE400; Mid: $00000002; Hi: $00000000; Flags: $00000000),
    (Lo: $4876E800; Mid: $00000017; Hi: $00000000; Flags: $00000000),
    (Lo: $D4A51000; Mid: $000000E8; Hi: $00000000; Flags: $00000000),
    (Lo: $4E72A000; Mid: $00000918; Hi: $00000000; Flags: $00000000),
    (Lo: $107A4000; Mid: $00005AF3; Hi: $00000000; Flags: $00000000),
    (Lo: $A4C68000; Mid: $00038D7E; Hi: $00000000; Flags: $00000000),
    (Lo: $6FC10000; Mid: $002386F2; Hi: $00000000; Flags: $00000000),
    (Lo: $5D8A0000; Mid: $01634578; Hi: $00000000; Flags: $00000000),
    (Lo: $A7640000; Mid: $0DE0B6B3; Hi: $00000000; Flags: $00000000),
    (Lo: $89E80000; Mid: $8AC72304; Hi: $00000000; Flags: $00000000),
    (Lo: $63100000; Mid: $6BC75E2D; Hi: $00000005; Flags: $00000000),
    (Lo: $DEA00000; Mid: $35C9ADC5; Hi: $00000036; Flags: $00000000),
    (Lo: $B2400000; Mid: $19E0C9BA; Hi: $0000021E; Flags: $00000000),
    (Lo: $F6800000; Mid: $02C7E14A; Hi: $0000152D; Flags: $00000000),
    (Lo: $A1000000; Mid: $1BCECCED; Hi: $0000D3C2; Flags: $00000000),
    (Lo: $4A000000; Mid: $16140148; Hi: $00084595; Flags: $00000000),
    (Lo: $E4000000; Mid: $DCC80CD2; Hi: $0052B7D2; Flags: $00000000),
    (Lo: $E8000000; Mid: $9FD0803C; Hi: $033B2E3C; Flags: $00000000),
    (Lo: $10000000; Mid: $3E250261; Hi: $204FCE5E; Flags: $00000000)
  );

type
  TAccumulator = packed record
    case Byte of
      0: (L0, L1, L2, L3, L4, L5, L6: Longword;
          Flags: Longword;
          Underflow: Longword);
      1: (Bits: array [0..6] of Longword;
          Reserved: Word;
          Scale: Shortint;
          Sign: ByteBool);
  end;

  TUnsigned64 = packed record
    case Byte of
      0: (UI64: UInt64);
      1: (Lo32: Longword;
          Hi32: Longword);
  end;

type
  TDecimalErrorType = (detOverflow, detUnderflow, detZeroDivide, detInvalidOp,
    detParse, detConversion, detInvalidArg, detNaN);

procedure DumpAccu(const A: TAccumulator);
begin
  if DoDump then
    Writeln(Format('Accu: %.2d %.8x %.8x %.8x %.8x %.8x %.8x', [A.Scale, A.L5, A.L4, A.L3, A.L2, A.L1, A.L0]));
end;

procedure Error(N: TDecimalErrorType; const S: string = '');
begin
  case N of
    detOverflow:
      raise EOverflow.Create(SDecimalOverflow);
    detUnderflow:
      raise EUnderflow.Create(SDecimalUnderflow);
    detZeroDivide:
      raise EZeroDivide.Create(SDecimalZeroDivide);
    detParse:
      raise EConvertError.CreateFmt(SErrorDecimalParsing, [S]);
    detConversion:
      raise EConvertError.CreateFmt(SConversionFailed, [S]);
    detInvalidArg:
      raise EInvalidArgument.CreateFmt(SInvalidArg, [S]);
    detNaN:
      raise EInvalidArgument.Create(SNaN);
  else
    raise EInvalidOp.Create(SDecimalInvalidOp);
  end;
end;

function StrToDecimal(const S: string): Decimal;
begin
  if not Decimal.TryParse(S, Result) then
    Error(detParse, S);
end;

function DecimalToStr(const D: Decimal): string;
begin
  Result := D.ToString;
end;

// Scales decimal down, i.e. divides Hi:Mid:Lo by 10 and decrements Scale
// without any integrity checks.  Returns remainder of division (0..9).

function DecScaleDownRaw(var D: Decimal): Byte;
asm
        PUSH    EDI

        MOV     EDI,EAX
        MOV     ECX,10
        XOR     EDX,EDX
        MOV     EAX,[EDI].Decimal.Hi
        DIV     ECX
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,[EDI].Decimal.Mid
        DIV     ECX
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,[EDI].Decimal.Lo
        DIV     ECX
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EAX,EDX

        DEC     [EDI].Decimal.Scale

        POP     EDI
end;
// Scales decimal down, i.e. divides "mantissa" by powers of 10 and decrements
// Scale, without any integrity checks. Rounds Result.

procedure DecScaleDownMaxRaw(var D: Decimal; Max: Integer);
asm
        PUSH    EDI
        MOV     EDI,EAX

        CMP     EDX,9
        JBE     @MaxOK
        MOV     EDX,9

@MaxOK:

        SUB     [EDI].Decimal.Scale,DL
        MOV     ECX,DWORD PTR [PowersOfTen+4*EDX]
        XOR     EDX,EDX
        MOV     EAX,[EDI].Decimal.Hi
        DIV     ECX
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,[EDI].Decimal.Mid
        DIV     ECX
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,[EDI].Decimal.Lo
        DIV     ECX
        MOV     [EDI].Decimal.Lo,EAX

        SHR     ECX,1                   // PowersOfTen[Max] div 2;
        CMP     EDX,ECX                 // compare to Remainder
        JG      @RoundUp
        JL      @Rounded
        TEST    [EDI].Decimal.Lo,1      // if Odd(Lo) then
        JE      @Rounded                //   round up

@RoundUp:

        ADD     [EDI].Decimal.Lo,1
        ADC     [EDI].Decimal.Mid,0
        ADC     [EDI].Decimal.Hi,0

@Rounded:

        POP     EDI
end;

procedure DecScaleDownTo(var D: Decimal; TargetScale: Byte);
asm
        PUSH    ESI
        PUSH    EBX

        MOV     ESI,EAX
        MOV     EBX,EDX
        JMP     @Loop

@TopLoop:

        MOV     EAX,ESI
        CALL    DecScaleDownRaw

@Loop:

        TEST    [ESI].Decimal.Lo,1
        JNE     @Exit
        MOVZX   EAX,[ESI].Decimal.Scale
        CMP     EAX,EBX
        JLE     @Exit
        MOV     EAX,ESI
        CALL    Decimal.Mod96by5
        OR      EAX,EAX
        JE      @TopLoop

@Exit:

        POP     EBX
        POP     ESI
end;

// Scales decimal up, i.e. multiplies "mantissa" by 10 and increments Scale,
// without any integrity checks.  Returns any EDX overflow.

function DecScaleUpRaw(var D: Decimal): Longword;
asm
        PUSH    EDI
        PUSH    EBX

        MOV     EDI,EAX
        MOV     ECX,10
        MOV     EAX,[EDI].Decimal.Lo
        MUL     ECX
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EBX,EDX
        MOV     EAX,[EDI].Decimal.Mid
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EBX,EDX
        MOV     EAX,[EDI].Decimal.Hi
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,EDX
        INC     [EDI].Decimal.Scale

        POP     EBX
        POP     EDI
end;

function DecScaleUpMax(var D: Decimal; Max: Integer): Boolean;
asm
        PUSH    EDI
        PUSH    EBX

        CMP     EDX,9
        JBE     @GetMaxScaleLoop
        MOV     EDX,9

@GetMaxScaleLoop:

        MOV     EDI,EAX
        MOV     ECX,DWORD PTR [MaxMultiplicands+4*EDX]
        CMP     [EDI].Decimal.Hi,ECX
        JB      @ScalesFine
        JA      @ScalesBad
        MOV     EAX,DWORD PTR [MaxMultiplicandsMid+4*EDX]
        JB      @ScalesFine

@ScalesBad:

        DEC     EDX
        JNE     @GetMaxScaleLoop
        XOR     EAX,EAX
        JMP     @Exit

@ScalesFine:

        ADD     [EDI].Decimal.Scale,DL
        MOV     ECX,DWORD PTR [PowersOfTen+4*EDX]
        MOV     EAX,[EDI].Decimal.Lo
        MUL     ECX
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EBX,EDX
        MOV     EAX,[EDI].Decimal.Mid
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EBX,EDX
        MOV     EAX,[EDI].Decimal.Hi
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,1

@Exit:

        POP     EBX
        POP     EDI
end;

// Scales decimal up, i.e. multiplies "mantissa" by 10 and increments Scale,
// then adds Num to "mantissa", without any integrity checks.
// Returns any overflow.
function DecScaleUpAndAdd(var D: Decimal; Num: Byte): Longword;
asm
        PUSH    EDI
        PUSH    EBX

        MOV     EDI,EAX
        MOVZX   EBX,Num
        CALL    DecScaleUpRaw
        MOV     ECX,EAX
        ADD     [EDI].Decimal.Lo,EBX
        ADC     [EDI].Decimal.Mid,0
        ADC     [EDI].Decimal.Hi,0
        ADC     ECX,0
        MOV     EAX,ECX

        POP     EBX
        POP     EDI
end;

// Scales accumulator down, i.e. divides it by 10 and increments Scale.
// No integrity checks.
procedure AccuScaleDownRaw(var A: TAccumulator);
asm
        PUSH    EDI

        MOV     EDI,EAX
        MOV     ECX,10

        DEC     [EDI].TAccumulator.Scale
        XOR     EDX,EDX
        MOV     EAX,[EDI].TAccumulator.L5
        DIV     ECX
        MOV     [EDI].TAccumulator.L5,EAX
        MOV     EAX,[EDI].TAccumulator.L4
        DIV     ECX
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     EAX,[EDI].TAccumulator.L3
        DIV     ECX
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EAX,[EDI].TAccumulator.L2
        DIV     ECX
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EAX,[EDI].TAccumulator.L1
        DIV     ECX
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EAX,[EDI].TAccumulator.L0
        DIV     ECX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     EAX,[EDI].TAccumulator.Underflow
        DIV     ECX
        MOV     [EDI].TAccumulator.Underflow,EAX

        POP     EDI
end;

procedure AccuScaleDownRaw128(var A: TAccumulator);
asm
        PUSH    EDI

        MOV     EDI,EAX
        MOV     ECX,10

        DEC     [EDI].TAccumulator.Scale
        XOR     EDX,EDX
        MOV     EAX,[EDI].TAccumulator.L3
        DIV     ECX
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EAX,[EDI].TAccumulator.L2
        DIV     ECX
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EAX,[EDI].TAccumulator.L1
        DIV     ECX
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EAX,[EDI].TAccumulator.L0
        DIV     ECX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     EAX,[EDI].TAccumulator.Underflow
        DIV     ECX
        MOV     [EDI].TAccumulator.Underflow,EAX

        POP     EDI
end;

procedure AccuScaleDownMax(var A: TAccumulator; Max: Integer);
asm
        PUSH    EDI

        MOV     EDI,EAX

        CMP     EDX,9
        JBE     @MaxOK
        MOV     EDX,9

@MaxOK:

        CMP     [EDI].TAccumulator.Scale,DL
        JAE     @MaxScaleOK
        MOV     DL,[EDI].TAccumulator.Scale

@MaxScaleOK:

        MOV     ECX,DWORD PTR [PowersOfTen+4*EDX]
        SUB     [EDI].TAccumulator.Scale,DL
        XOR     EDX,EDX

        MOV     EAX,[EDI].TAccumulator.L5
        DIV     ECX
        MOV     [EDI].TAccumulator.L5,EAX
        MOV     EAX,[EDI].TAccumulator.L4
        DIV     ECX
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     EAX,[EDI].TAccumulator.L3
        DIV     ECX
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EAX,[EDI].TAccumulator.L2
        DIV     ECX
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EAX,[EDI].TAccumulator.L1
        DIV     ECX
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EAX,[EDI].TAccumulator.L0
        DIV     ECX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     EAX,[EDI].TAccumulator.Underflow
        DIV     ECX
        MOV     [EDI].TAccumulator.Underflow,EAX

        POP     EDI

end;

procedure AccuScaleDownMax128(var A: TAccumulator; Max: Integer);
asm
        PUSH    EDI

        MOV     EDI,EAX

        CMP     EDX,9
        JBE     @MaxOK
        MOV     EDX,9

@MaxOK:

        CMP     [EDI].TAccumulator.Scale,DL
        JAE     @MaxScaleOK
        MOV     DL,[EDI].TAccumulator.Scale

@MaxScaleOK:

        MOV     ECX,DWORD PTR [PowersOfTen+4*EDX]
        SUB     [EDI].TAccumulator.Scale,DL
        XOR     EDX,EDX

        MOV     EAX,[EDI].TAccumulator.L3
        DIV     ECX
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EAX,[EDI].TAccumulator.L2
        DIV     ECX
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EAX,[EDI].TAccumulator.L1
        DIV     ECX
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EAX,[EDI].TAccumulator.L0
        DIV     ECX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     EAX,[EDI].TAccumulator.Underflow
        DIV     ECX
        MOV     [EDI].TAccumulator.Underflow,EAX

        MOV     EAX,EDX

        POP     EDI

end;

// Scales accumulator up, i.e. multiplies it by 10 and increments Scale.
// Returns any overflow.

procedure AccuScaleUp(var A: TAccumulator);
asm
        PUSH    EDI
        PUSH    EBX

        MOV     EDI,EAX
        CMP     [EDI].TAccumulator.Scale,28
        JGE     @Exit
        INC     [EDI].TAccumulator.Scale
        MOV     ECX,10

        MOV     EAX,[EDI].TAccumulator.L0
        MUL     ECX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L1
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L2
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L3
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L4
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L5
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L5,EAX

        MOV     EAX,EDX
@Exit:

        POP     EBX
        POP     EDI
end;

// Scales accumulator up, i.e. multiplies it by powers of 10 and increments
// Scale accordingly. Performs integrity checks.
// Returns any overflow.
function AccuScaleUpMax(var A: TAccumulator; Max: Integer): Longword;
asm
        PUSH    EDI
        PUSH    EBX

        MOV     EDI,EAX

        CMP     EDX,9
        JBE     @MaxOK
        MOV     EDX,9
        MOV     EAX,-1

@MaxOK:

        MOV     ECX,DWORD PTR [MaxMultiplicands+4*EDX]
        CMP     ECX,[EDI].TAccumulator.L5
        JA      @TopLwOk
        JB      @DecrementOne
        MOV     ECX,DWORD PTR [MaxMultiplicandsMid+4*EDX]
        CMP     ECX,[EDI].TAccumulator.L4
        JAE     @TopLWOk

@DecrementOne:

        DEC     EDX
        JE      @Exit
        JMP     @MaxOK

@TopLwOk:

        MOV     CL,28
        SUB     CL,[EDI].TAccumulator.Scale
        CMP     DL,CL
        JLE     @MaxScaleOK
        MOV     DL,CL

@MaxScaleOK:

        ADD     [EDI].TAccumulator.Scale,DL
        MOV     ECX,DWORD PTR [PowersOfTen+4*EDX]

        MOV     EAX,[EDI].TAccumulator.L0
        MUL     ECX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L1
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L2
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L3
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L4
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L5
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L5,EAX

        MOV     EAX,EDX

@Exit:

        POP     EBX
        POP     EDI
end;

function AccuScaleUpMax224(var A: TAccumulator; Max: Integer): Longword;
asm
        PUSH    EDI
        PUSH    EBX

        MOV     EDI,EAX

        CMP     EDX,9
        JBE     @MaxOK
        MOV     EDX,9
        MOV     EAX,-1

@MaxOK:

        MOV     ECX,DWORD PTR [MaxMultiplicands+4*EDX]
        CMP     ECX,[EDI].TAccumulator.L6
        JA      @TopLwOk
        JB      @DecrementOne
        MOV     ECX,DWORD PTR [MaxMultiplicandsMid+4*EDX]
        CMP     ECX,[EDI].TAccumulator.L5
        JAE     @TopLWOk

@DecrementOne:

        DEC     EDX
        JE      @Exit
        JMP     @MaxOK

@TopLwOk:

        MOV     CL,28
        SUB     CL,[EDI].TAccumulator.Scale
        CMP     DL,CL
        JLE     @MaxScaleOK
        MOV     DL,CL

@MaxScaleOK:

        ADD     [EDI].TAccumulator.Scale,DL
        MOV     ECX,DWORD PTR [PowersOfTen+4*EDX]

        MOV     EAX,[EDI].TAccumulator.L0
        MUL     ECX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L1
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L2
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L3
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L4
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L5
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L5,EAX
        MOV     EBX,EDX

        MOV     EAX,[EDI].TAccumulator.L6
        MUL     ECX
        ADD     EAX,EBX
        ADC     EDX,0
        MOV     [EDI].TAccumulator.L6,EAX

        MOV     EAX,EDX

@Exit:

        POP     EBX
        POP     EDI
end;

{ Decimal }

class function Decimal.Abs(const D: Decimal): Decimal;
begin
  if @Result <> @D then
    Result := D;
  Result.Sign := 0;
end;

class function Decimal.Add(const Left, Right: Decimal): Decimal;
begin
  InternalAdd(Result, Left, Right);
end;

class operator Decimal.Add(const Left, Right: Decimal): Decimal;
begin
  InternalAdd(Result, Left, Right);
end;

class function Decimal.Ceiling(const D: Decimal): Decimal;
begin
  InternalCeiling(Result, D);
end;

class function Decimal.Compare(const Left, Right: Decimal): TValueSign;
var
  L, R: TAccumulator;
asm
        PUSH    EDI

        // Copy Left decimal to L
        MOV     EDI,EAX
        MOV     EAX,[EDI].Decimal.Lo
        MOV     L.L0,EAX
        MOV     EAX,[EDI].Decimal.Mid
        MOV     L.L1,EAX
        MOV     EAX,[EDI].Decimal.Hi
        MOV     L.L2,EAX
        MOV     EAX,[EDI].Decimal.Flags
        MOV     L.Flags,EAX
        XOR     EAX,EAX
        MOV     L.L3,EAX
        MOV     L.L4,EAX
        MOV     L.L5,EAX
        MOV     L.Underflow,EAX

        // Copy Right decimal to R

        MOV     EAX,[EDX].Decimal.Lo
        MOV     R.L0,EAX
        MOV     EAX,[EDX].Decimal.Mid
        MOV     R.L1,EAX
        MOV     EAX,[EDX].Decimal.Hi
        MOV     R.L2,EAX
        MOV     EAX,[EDX].Decimal.Flags
        MOV     R.Flags,EAX
        XOR     EAX,EAX
        MOV     R.L3,EAX
        MOV     R.L4,EAX
        MOV     R.L5,EAX

        MOV     EAX,L.L0
        OR      EAX,L.L1
        OR      EAX,L.L2
        OR      EAX,R.L0
        OR      EAX,R.L1
        OR      EAX,R.L2
        JNE     @NotBothZero
        XOR     EAX,EAX
        JMP     @Finish

@NotBothZero:

        MOV     AL,L.Sign
        CMP     AL,R.Sign
        JE      @LeftScaleLoop
        JA      @Below
        MOV     EAX,1
        JMP     @Finish

@Below:

        MOV     EAX,-1
        JMP     @Finish

@LeftScaleLoop:

        // If necessary, scale L up

        MOV     AL,R.Scale
        SUB     AL,L.Scale
        JE      @DoComparison
        JL      @RightScaleLoop
        MOVZX   EDX,AL
        LEA     EAX,L
        CALL    AccuScaleUpMax
        JMP     @LeftScaleLoop

@RightScaleLoop:

        // If necessary, scale R up

        MOV     AL,L.Scale
        SUB     AL,R.Scale
        JE      @DoComparison
        MOVZX   EDX,AL
        LEA     EAX,R
        CALL    AccuScaleUpMax
        JMP     @RightScaleLoop

@DoComparison:

        // Compare L and R, top down

        MOV     EAX,1
        MOV     EDX,L.L5
        CMP     EDX,R.L5
        JA      @Greater
        JB      @Less
        MOV     EDX,L.L4
        CMP     EDX,R.L4
        JA      @Greater
        JB      @Less
        MOV     EDX,L.L3
        CMP     EDX,R.L3
        JA      @Greater
        JB      @Less
        MOV     EDX,L.L2
        CMP     EDX,R.L2
        JA      @Greater
        JB      @Less
        MOV     EDX,L.L1
        CMP     EDX,R.L1
        JA      @Greater
        JB      @Less
        MOV     EDX,L.L0
        CMP     EDX,R.L0
        JA      @Greater
        JB      @Less
        JMP     @Equal

@Less:

        DEC     EAX

@Equal:

        DEC     EAX

@Greater:

        CMP     L.Sign,0
        JE      @Finish
        NEG     EAX

@Finish:

        POP     EDI
end;

function Decimal.CompareTo(const D: Decimal): TValueSign;
begin
  Result := Decimal.Compare(Self, D);
end;

constructor Decimal.Create(From: Int64);
var
  Sign: Boolean;
begin
  Sign := From < 0;
  if Sign then
    From := -From;
  Create(From and $FFFFFFFF, From shr 32, 0, Sign, 0);
end;

constructor Decimal.Create(From: UInt64);
begin
  Create(From and $FFFFFFFF, From shr 32, 0, False, 0);
end;

constructor Decimal.Create(From: Integer);
var
  Sign: Boolean;
begin
  Sign := From < 0;
  if Sign then
    From := -From;
  Create(From, 0, 0, Sign, 0);
end;

constructor Decimal.Create(From: Longword);
begin
  Create(From, 0, 0, False, 0);
end;

constructor Decimal.Create(From: Extended);
begin
  InternalFromExtended(Self, From);
end;

constructor Decimal.Create(From: array of Longword);
begin
  if Length(From) = 4 then
  begin
    Lo := From[0];
    Mid := From[1];
    Hi := From[2];
    Flags := From[3];
  end;
end;

constructor Decimal.Create(Lo, Mid, Hi: Longword; IsNegative: Boolean;
  scale: Byte);
begin
  Self.Lo := Lo;
  Self.Mid := Mid;
  Self.Hi := Hi;
  Flags := 0;
  if IsNegative then
    Sign := $80;
  Self.Scale := scale;
end;

constructor Decimal.Create(From: Currency);
var
  Sign: Boolean;
begin
  Sign := From < 0;
  if Sign then
    From := -From;
  Create(PLongword(@From)[0], PLongword(@From)[1], 0, Sign, 4);
end;

class operator Decimal.Dec(const D: Decimal): Decimal;
begin
  Result := D + Decimal.MinusOne;
end;

class function Decimal.Divide(const Left, Right: Decimal): Decimal;
begin
  InternalDivide(Result, Left, Right);
end;

class operator Decimal.Divide(const Left, Right: Decimal): Decimal;
begin
  InternalDivide(Result, Left, Right);
end;

class operator Decimal.Equal(const Left, Right: Decimal): Boolean;
begin
  Result := Compare(Left, Right) = 0;
end;

function Decimal.Equals(const D: Decimal): Boolean;
begin
  Result := Decimal.Compare(Self, D) = 0;
end;

class function Decimal.Equals(const Left, Right: Decimal): Boolean;
begin
  Result := Decimal.Compare(Left, Right) = 0;
end;

class function Decimal.Floor(const D: Decimal): Decimal;
begin
  InternalFloor(Result, D);
end;

class function Decimal.Frac(const D: Decimal): Decimal;
begin
  Result := D - Decimal.Truncate(D);
end;

class procedure Decimal.InternalFromDouble(out Result: Decimal; const Source: Double);
var
  A: TAccumulator;
  LResult: ^Decimal;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        MOV     LResult,EAX

        XOR     EAX,EAX
        MOV     A.L0,EAX
        MOV     A.L1,EAX
        MOV     A.L2,EAX
        MOV     A.L3,EAX
        MOV     A.L4,EAX
        MOV     A.L5,EAX
        MOV     A.Flags,EAX

        MOV     ESI,DWORD PTR [Source]
        MOV     EBX,DWORD PTR [Source+4]
        MOV     EDX,EBX
        AND     EBX,DoubleMMask
        OR      EBX,DoubleMMask+1
        SHLD    EBX,ESI,DoubleMShift
        SHL     ESI,DoubleMShift        // mantissa in EBX:ESI

        SHR     EDX,DoubleEShift
        TEST    EDX,DoubleEMask+1
        JE      @Positive
        OR      A.Sign,$80

@Positive:

        AND     EDX,DoubleEMask
        JE      @Finish
        CMP     EDX,DoubleEMask
        JNE     @NoSpecialValue
        CMP     EBX,$80000000
        JNE     @NaN
        OR      ESI,ESI
        JE      @Infinite

@NaN:

        MOV     EAX,detNaN
        JMP     Error

@Infinite:

        MOV     EAX,$FFFFFFFF
        MOV     A.L3,EAX
        MOV     A.L4,EAX
        MOV     A.L5,EAX
        JMP     @Finish

@NoSpecialValue:

        SUB     EDX,DoubleBias-96
        JNS     @NoUnderflow
        MOV     EAX,detUnderflow
        JMP     Error

@NoUnderflow:

        CMP     EDX,191
        JBE     @NoOverflow
        MOV     EAX,detOverflow
        JMP     Error

@NoOverflow:

        MOV     CL,DL
        NOT     CL
        XOR     EAX,EAX
        SHR     EDX,5
        SHRD    EAX,ESI,CL
        SHRD    ESI,EBX,CL
        SHR     EBX,CL                  // Shifted mantissa in EBX:ESI:EAX

        MOV     DWORD PTR [A+4*EDX],EBX
        DEC     EDX
        JNS     @StoreESI
        CMP     ESI,$80000000
        JB      @ScaleLoop
        JA      @DoRound0
        TEST    EBX,1
        JE      @ScaleLoop

@DoRound0:

        ADD     A.L0,1
        ADC     A.L1,0
        JMP     @ScaleLoop

@StoreESI:

        MOV     DWORD PTR [A+4*EDX],ESI
        DEC     EDX
        JNS     @StoreEAX
        CMP     EAX,$80000000
        JB      @ScaleLoop
        JA      @DoRound1
        TEST    ESI,1
        JE      @ScaleLoop

@DoRound1:

        ADD     A.L0,1
        ADC     A.L1,0
        ADC     A.L2,0
        JMP     @ScaleLoop

@StoreEAX:

        MOV     DWORD PTR [A+4*EDX],EAX

@ScaleLoop:

        CMP     A.Scale,28
        JGE     @Finish
        MOV     EAX,A.L0
        OR      EAX,A.L1
        OR      EAX,A.L2
        JE      @Finish
        CMP     A.L5,HLWdiv10
        JA      @Finish
        LEA     EAX,A
        CALL    AccuScaleUp
        JMP     @ScaleLoop

@Finish:

        MOV     EDI,LResult
        MOV     EAX,A.L3
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EAX,A.L4
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,A.L5
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,A.Flags
        MOV     [EDI].Decimal.Flags,EAX

        POP     EBX
        POP     ESI
        POP     EDI
end;

// Extended
// Bits 0-63: Mantissa, no hidden bit
// Bits 64-78: Exponent, biased
// Bit 79: Sign

class procedure Decimal.InternalFromExtended(out Result: Decimal; const Source: Extended);
var
  A: TAccumulator;
  LResult: ^Decimal;
asm
        PUSH    ESI
        PUSH    EDI
        PUSH    EBX
        MOV     LResult,EAX

        XOR     EAX,EAX
        MOV     A.L0,EAX
        MOV     A.L1,EAX
        MOV     A.L2,EAX
        MOV     A.L3,EAX
        MOV     A.L4,EAX
        MOV     A.L5,EAX
        MOV     A.Flags,EAX
        MOV     EDI,DWORD PTR [Source]
        MOV     ESI,DWORD PTR [Source+4]
        MOVZX   EDX,WORD PTR [Source+8]
        MOV     CH,DH
        AND     CH,$80
        MOV     A.Sign,CH
        AND     EDX,ExtendedEMask
        JE      @Finish
        CMP     EDX,ExtendedEMask
        JNE     @NoSpecialValue
        CMP     ESI,$80000000
        JNE     @NaN
        OR      EDI,EDI
        JE      @Infinite

@NaN:

        MOV     EAX,detNaN
        CALL    Error

@Infinite:

        MOV     EAX,-1
        MOV     A.L3,EAX
        MOV     A.L4,EAX
        MOV     A.L5,EAX
        JMP     @Finish

@NoSpecialValue:

        SUB     EDX,ExtendedBias-97
        CMP     EDX,$000000BF
        JBE     @NoOverflow
        MOV     EAX,detOverflow
        JMP     Error

@NoOverflow:

        CMP     EDX,0
        JGE     @NoUnderflow
        MOV     EAX,detUnderflow
        JMP     Error

@NoUnderflow:

        XOR     EBX,EBX
        MOV     ECX,EDX
        SHR     EDX,5
        XOR     EAX,EAX
        NEG     ECX
        AND     ECX,$1F
        JE      @ZeroShift
        SHRD    EBX,EDI,CL
        SHRD    EDI,ESI,CL
        SHR     ESI,CL
        JMP     @Shifted

@ZeroShift:

        DEC     EDX

@Shifted:

        MOV     DWORD PTR [A+4*EDX],ESI
        DEC     EDX
        JS      @ScaleUpLoop
        MOV     DWORD PTR [A+4*EDX],EDI
        DEC     EDX
        JS      @ScaleUpLoop
        MOV     DWORD PTR [A+4*EDX],EBX

@ScaleUpLoop:

        MOV     EAX,A.L0
        OR      EAX,A.L1
        OR      EAX,A.L2
        JE      @Finish
        CMP     A.Scale,28
        JAE     @Finish
        CMP     A.L5,HLWdiv10
        JA      @Finish
        LEA     EAX,A
        CALL    AccuScaleUp
        JMP     @ScaleUpLoop

@Finish:

        MOV     EDI,LResult
        MOV     EAX,A.L3
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EAX,A.L4
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,A.L5
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,A.Flags
        MOV     [EDI].Decimal.Flags,EAX

        POP     EBX
        POP     EDI
        POP     ESI
end;

class procedure Decimal.InternalFromSingle(out Result: Decimal; const Source: Single);
var
  A: TAccumulator;
  LResult: ^Decimal;
asm
        PUSH    ESI
        PUSH    EDI
        MOV     LResult,EAX

        XOR     EAX,EAX
        MOV     A.L0,EAX
        MOV     A.L1,EAX
        MOV     A.L2,EAX
        MOV     A.L3,EAX
        MOV     A.L4,EAX
        MOV     A.L5,EAX
        MOV     A.Flags,EAX

        MOV     ESI,Source
        MOV     EDX,ESI
        AND     ESI,SingleMMask         // 23 bit stored mantissa
        OR      ESI,SingleMMask+1       // set hidden bit
        SHL     ESI,SingleMShift        // shift up 8 bits

        SHR     EDX,SingleEShift
        OR      DH,DH
        JE      @Positive
        OR      A.Sign,$80
        XOR     DH,DH

@Positive:
        OR      DL,DL

        JE      @Finish
        CMP     DL,$FF
        JNE     @NoSpecialValue
        CMP     ESI,$80000000
        JE      @Infinite
        MOV     EAX,detNaN
        JMP     Error

@Infinite:

        MOV     EAX,-1
        MOV     A.L3,EAX
        MOV     A.L4,EAX
        MOV     A.L5,EAX
        JMP     @Finish

@NoSpecialValue:

        SUB     DL,SingleBias-96
        MOV     CL,DL
        NOT     CL

        XOR     EAX,EAX
        SHR     EDX,5
        SHRD    EAX,ESI,CL     // ESI: top bits
        SHR     ESI,CL         // EAX: bottom bits

        MOV     DWORD PTR [A+4*EDX],ESI
        DEC     EDX
        JNS     @StoreEAX
        CMP     EAX,$80000000
        JB      @InPosition
        JA      @DoRound
        TEST    ESI,1
        JE      @InPosition

@DoRound:

        ADD     A.L0,1
        ADC     A.L1,0
        JMP     @InPosition

@StoreEAX:

        MOV     DWORD PTR [A+4*EDX],EAX

@InPosition:

        CMP     A.Scale,28
        JGE     @Finish
        MOV     EAX,A.L0
        OR      EAX,A.L1
        OR      EAX,A.L2
        JE      @Finish
        LEA     EAX,A
        CALL    AccuScaleUp
        JMP     @InPosition

@Finish:

        MOV     EDI,LResult
        MOV     EAX,A.L3
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EAX,A.L4
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,A.L5
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,A.Flags
        MOV     [EDI].Decimal.Flags,EAX

        POP     EDI
        POP     ESI
end;

class function Decimal.GetBits(const D: Decimal): TLongwordArray;
begin
  SetLength(Result, SizeOf(Decimal) div SizeOf(Longword));
  Move(D, Result[0], SizeOf(Decimal));
end;

function Decimal.GetBytes: TBytes;
begin
  SetLength(Result, SizeOf(Decimal));
  Move(Self, Result[0], SizeOf(Decimal));
end;

class operator Decimal.GreaterThan(const Left, Right: Decimal): Boolean;
begin
  Result := Compare(Left, Right) > 0;
end;

class operator Decimal.GreaterThanOrEqual(const Left, Right: Decimal): Boolean;
begin
  Result := Compare(Left, Right) >= 0;
end;

class operator Decimal.Implicit(const C: Cardinal): Decimal;
begin
  Result := Decimal.Create(C);
end;

class operator Decimal.Implicit(const UI64: UInt64): Decimal;
begin
  Result := Decimal.Create(UI64);
end;

class operator Decimal.Implicit(const S: Single): Decimal;
begin
  InternalFromSingle(Result, S);
end;

class operator Decimal.Implicit(const D: Decimal): Int64;
begin
  Result := D.ToInt64;
end;

class operator Decimal.Implicit(const D: Decimal): Extended;
begin
  Result := D.ToExtended;
end;

class operator Decimal.Implicit(const D: Decimal): Currency;
begin
  Result := D.ToCurrency;
end;

class operator Decimal.Implicit(const D: Decimal): UInt64;
begin
  Result := D.ToUInt64;
end;

class operator Decimal.Implicit(const D: Decimal): Longword;
begin
  Result := D.ToLongword;
end;

class operator Decimal.Implicit(const D: Decimal): Longint;
begin
  Result := D.ToLongint;
end;

class operator Decimal.Implicit(const D: Decimal): string;
begin
  Result := D.ToString;
end;

class operator Decimal.Implicit(const I: Integer): Decimal;
begin
  Result := Decimal.Create(I);
end;

class operator Decimal.Implicit(const I64: Int64): Decimal;
begin
  Result := Decimal.Create(I64);
end;

class operator Decimal.Implicit(const E: Extended): Decimal;
begin
  InternalFromExtended(Result, E);
end;

class operator Decimal.Implicit(const D: Double): Decimal;
begin
  InternalFromDouble(Result, D);
end;

class operator Decimal.Implicit(const S: string): Decimal;
begin
  Result := Decimal.Parse(S);
end;

class operator Decimal.Implicit(const c: Currency): Decimal;
begin
  Result := Decimal.Create(c);
end;

class operator Decimal.Inc(const D: Decimal): Decimal;
begin
  Result := D + Decimal.One;
end;

class procedure Decimal.InternalAdd(var Result: Decimal; const Left, Right: Decimal);
begin
  InternalAddSub(Result, Left, Right, 0);
end;

class procedure Decimal.InternalAddSub(var Result: Decimal; const Left, Right: Decimal; Sign: Byte);
var
  L, R: TAccumulator;
asm
        PUSH    EBX

        MOV     EBX,EAX
        XOR     EAX,EAX

        // Copy Left decimal into L

        MOV     EAX,[EDX].Decimal.Lo
        MOV     L.L0,EAX
        MOV     EAX,[EDX].Decimal.Mid
        MOV     L.L1,EAX
        MOV     EAX,[EDX].Decimal.Hi
        MOV     L.L2,EAX
        MOV     EAX,[EDX].Decimal.Flags
        MOV     L.Flags,EAX
        XOR     EAX,EAX
        MOV     L.L3,EAX
        MOV     L.L4,EAX
        MOV     L.L5,EAX
        MOV     L.Underflow,EAX

        // Copy Right decimal into R

        MOV     EAX,[ECX].Decimal.Lo
        MOV     R.L0,EAX
        MOV     EAX,[ECX].Decimal.Mid
        MOV     R.L1,EAX
        MOV     EAX,[ECX].Decimal.Hi
        MOV     R.L2,EAX
        MOV     EAX,[ECX].Decimal.Flags
        MOV     R.Flags,EAX
        MOV     AL,Sign
        XOR     R.Sign,AL
        XOR     EAX,EAX
        MOV     R.L3,EAX
        MOV     R.L4,EAX
        MOV     R.L5,EAX

        // If necessary, scale L up to match scale of R

        MOV     AL,L.Scale
        SUB     AL,R.Scale
        JE      @CheckSigns
        JA      @ScaleRightLoop
        NEG     AL

@ScaleLeftLoop:

        MOVZX   EDX,AL
        LEA     EAX,L
        CALL    AccuScaleUpMax
        MOV     AL,R.Scale
        SUB     AL,L.Scale
        JNE     @ScaleLeftLoop
        JMP     @CheckSigns

@ScaleRightLoop:

        MOVZX   EDX,AL
        LEA     EAX,R
        CALL    AccuScaleUpMax
        MOV     AL,L.Scale
        SUB     AL,R.Scale
        JNE     @ScaleRightLoop

@CheckSigns:

        MOV     AL,L.Sign
        XOR     AL,R.Sign
        JNE     @DoSubtract

        // L := L + R

        MOV     EAX,R.L0
        ADD     L.L0,EAX
        MOV     EAX,R.L1
        ADC     L.L1,EAX
        MOV     EAX,R.L2
        ADC     L.L2,EAX
        MOV     EAX,R.L3
        ADC     L.L3,EAX
        MOV     EAX,R.L4
        ADC     L.L4,EAX
        MOV     EAX,R.L5
        ADC     L.L5,EAX
        JMP     @ScaleDownLoop

@DoSubtract:

        // L := L - R.

        MOV     EAX,R.L0
        SUB     L.L0,EAX
        MOV     EAX,R.L1
        SBB     L.L1,EAX
        MOV     EAX,R.L2
        SBB     L.L2,EAX
        MOV     EAX,R.L3
        SBB     L.L3,EAX
        MOV     EAX,R.L4
        SBB     L.L4,EAX
        MOV     EAX,R.L5
        SBB     L.L5,EAX
        JNC     @ScaleDownLoop

        // if L < 0 then L := -L;

        XOR     L.Sign,$80
        XOR     EDX,EDX
        MOV     EAX,EDX
        SUB     EAX,L.L0
        MOV     L.L0,EAX
        MOV     EAX,EDX
        SBB     EAX,L.L1
        MOV     L.L1,EAX
        MOV     EAX,EDX
        SBB     EAX,L.L2
        MOV     L.L2,EAX
        MOV     EAX,EDX
        SBB     EAX,L.L3
        MOV     L.L3,EAX
        MOV     EAX,EDX
        SBB     EAX,L.L4
        MOV     L.L4,EAX
        MOV     EAX,EDX
        SBB     EAX,L.L5
        MOV     L.L5,EAX

        // Scale down until top 3 longwords are 0.

@ScaleDownLoop:

        CMP     L.L5,0
        JE      @CheckL4
        LEA     EAX,L
        MOV     EDX,9
        CALL    AccuScaleDownMax
        JMP     @ScaleDownLoop

@CheckL4:

        CMP     L.L4,0
        JE      @CheckL3
        LEA     EAX,L
        MOV     EDX,9
        CALL    AccuScaleDownMax
        JMP     @CheckL4

@CheckL3:

        CMP     L.L3,0
        JNE     @ScaleDown
        CMP     L.Scale,28
        JBE     @Finish

@ScaleDown:

        CMP     L.Scale,0
        JLE     @Finish
        LEA     EAX,L
        CALL    AccuScaleDownRaw
        JMP     @CheckL3

        // Check for overflow. The above loop is aborted if Scale outside its
        // range, so the top 3 longwords can still be <> 0.
@Finish:

        CMP     L.Underflow,$80000000
        JB      @NoRound
        JA      @DoRound
        CMP     L.L0,1
        JZ      @NoRound

@DoRound:

        ADD     L.L0,1
        ADC     L.L1,0
        ADC     L.L2,0

@NoRound:

        MOV     EAX,L.L5
        OR      EAX,L.L4
        OR      EAX,L.L3
        JE      @Exit
        MOV     EAX,detOverflow
        CALL    Error

        // Copy Flags:L2:L1:L0 into Result.
@Exit:

        MOV     EAX,L.L0
        MOV     [EBX].Decimal.Lo,EAX
        MOV     EAX,L.L1
        MOV     [EBX].Decimal.Mid,EAX
        MOV     EAX,L.L2
        MOV     [EBX].Decimal.Hi,EAX
        MOV     EAX,L.Flags
        MOV     [EBX].Decimal.Flags,EAX

        POP     EBX
end;

class procedure Decimal.InternalCeiling(var Result: Decimal; const Source: Decimal);
asm
        PUSH    EDI

        MOV     EDI,EAX
        CALL    InternalTruncate
        CMP     [EDI].Decimal.Sign,0
        JNE     @Exit
        ADD     [EDI].Decimal.Lo,1
        ADC     [EDI].Decimal.Mid,0
        ADC     [EDI].Decimal.Hi,0
@Exit:
        POP     EDI
end;

// Simple 192 by 32 bit division. Starts at top and uses remainder in EDX as high longword for
// division of lower longword.

class function Decimal.Div192by32(Quotient: PLongword; const Dividend: Decimal; Divisor: Longword): Longword;
asm

        PUSH    ESI
        PUSH    EDI

        MOV     EDI,EAX                 // EDI is Quotient
        MOV     ESI,EDX                 // ESI is Dividend
        CMP     ECX,0
        JNE     @NoZeroDivide32

        MOV     EAX,detZeroDivide
        JMP     Error

@NoZeroDivide32:

        XOR     EDX,EDX
        MOV     EAX,[ESI].Decimal.Hi
        DIV     ECX
        MOV     [EDI].TAccumulator.L6,EAX
        MOV     EAX,[ESI].Decimal.Mid
        DIV     ECX
        MOV     [EDI].TAccumulator.L5,EAX
        MOV     EAX,[ESI].Decimal.Lo
        DIV     ECX
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     EAX,0
        DIV     ECX
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     EAX,0
        DIV     ECX
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     EAX,0
        DIV     ECX
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     EAX,0
        DIV     ECX
        MOV     [EDI].TAccumulator.L0,EAX

        MOV     EAX,EDX

        POP     EDI
        POP     ESI
end;

// Basecase division
//
// The following two routines use Donald Knuth's algorithm D (Vol. 2, section 4.3.1) to do a
// faster division. It works (almost) like a normal long division, as taught in many schools,
// but unlike a binary long division, which has to loop once for every bit, it finds an entire
// digit (longword) in one pass. There are m - n + 1 passes, where m is the number of digits
// (or "limbs") of the dividend, and n the number of digits of the divisor.
//
// For this to work, the divisor must be "normalized", i.e. it is shifted left until the top bit
// is set. The dividend is shifted left accordingly, so the result remains the same.
//
// Just like in normal long division done manually, the divisor is aligned under the dividend,
// and a guess for the first result digit is made. In this algorithm this is achieved by dividing
// the top two digits of the dividend (or the remainder of it) by the top digit of the divisor. The
// guess can of course be too big or too high, and some adjustments are made. Then divisor*digit
// is subtracted from the aligned digits of the dividend (or its remainder). If the result is
// negative, the divisor is added back once and the estimated digit is decremented. Now the digit
// can be stored, and the loop repeats with the remainder of the dividend.

class function Decimal.Div192by64(Quotient: PLongword; const Dividend, Divisor: Decimal): Longword;
var
  LDivisor: Decimal;
  LDividend: array[0..7] of Longword;
  QHat: TUnsigned64;
  RHat: Longword;
  A: TAccumulator;
  Carry: Longword;
asm

        PUSH    ESI
        PUSH    EDI
        PUSH    EBX

        MOV     EDI,EAX                 // EDI = Quotient
        MOV     ESI,EDX                 // ESI = Dividend
        MOV     EBX,ECX                 // EBX = Divisor

        // Top Longword of Divisor is 0, so ignore it.

        MOV     EAX,[EBX].Decimal.Mid
        BSR     ECX,EAX
        SUB     ECX,31
        NEG     ECX
        JE      @NoShift64

        MOV     EDX,[EBX].Decimal.Lo
        SHLD    EAX,EDX,CL
        MOV     LDivisor.Mid,EAX
        SHL     EDX,CL
        MOV     LDivisor.Lo,EDX

        XOR     EAX,EAX
        MOV     EDX,[ESI].Decimal.Hi
        SHLD    EAX,EDX,CL
        MOV     DWORD PTR [LDividend+28],EAX
        MOV     EAX,[ESI].Decimal.Mid
        SHLD    EDX,EAX,CL
        MOV     DWORD PTR [LDividend+24],EDX
        MOV     EDX,[ESI].Decimal.Lo
        SHLD    EAX,EDX,CL
        MOV     DWORD PTR [LDividend+20],EAX
        SHL     EDX,CL
        MOV     DWORD PTR [LDividend+16],EDX
        JMP     @PrepareMainLoop64

@NoShift64:

        MOV     EAX,[EBX].Decimal.Mid
        MOV     LDivisor.Mid,EAX
        MOV     EAX,[EBX].Decimal.Lo
        MOV     LDivisor.Lo,EAX

        MOV     DWORD PTR [LDividend+28],0
        MOV     EAX,[ESI].Decimal.Hi
        MOV     DWORD PTR [LDividend+24],EAX
        MOV     EAX,[ESI].Decimal.Mid
        MOV     DWORD PTR [LDividend+20],EAX
        MOV     EAX,[ESI].Decimal.Lo
        MOV     DWORD PTR [LDividend+16],EAX

@PrepareMainLoop64:

        XOR     EAX,EAX
        MOV     LDivisor.Hi,EAX
        MOV     DWORD PTR [LDividend+12],EAX
        MOV     DWORD PTR [LDividend+8],EAX
        MOV     DWORD PTR [LDividend+4],EAX
        MOV     DWORD PTR [LDividend],EAX
        MOV     [EDI].TAccumulator.L6,EAX
        MOV     [EDI].TAccumulator.L5,EAX
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     [EDI].Taccumulator.L3,EAX
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     [EDI].TAccumulator.L0,EAX

        MOV     ECX,5           // m - n + 1 = 7 - 2 + 1 = 6 --> 0..5

@MainLoop64:

        XOR     EDX,EDX
        MOV     EBX,LDivisor.Mid
        MOV     EAX,DWORD PTR [LDividend+4*ECX+8]
        DIV     EBX
        MOV     QHat.Hi32,EAX
        MOV     EAX,DWORD PTR [LDividend+4*ECX+4]
        MOV     ESI,EAX
        DIV     EBX
        MOV     QHat.Lo32,EAX


        MUL     EBX
        MOV     A.L0,EAX
        MOV     A.L1,EDX
        MOV     EAX,QHat.Hi32
        MUL     EBX
        ADD     EAX,A.L1
        SUB     ESI,A.L0
        MOV     RHat,ESI

@AdjustLoop64:

        CMP     QHat.Hi32,0
        JNE     @DoAdjust64
        MOV     EBX,LDivisor.Lo
        MOV     EAX,QHat.Lo32
        MUL     EBX
        SUB     EAX,DWORD PTR [LDividend+4*ECX]
        SBB     EDX,RHat
        JBE     @AdjustLoopEnd64

@DoAdjust64:

        // This part is called at most 2 times (see Knuth).

        SUB     QHat.Lo32,1
        SBB     QHat.Hi32,0
        MOV     EAX,LDivisor.Mid
        ADD     RHat,EAX
        JZ      @AdjustLoop64

@AdjustLoopEnd64:

        MOV     EBX,QHat.Lo32
        MOV     EAX,LDivisor.Lo
        MUL     EBX
        MOV     DWORD PTR A,EAX
        MOV     ESI,EDX
        MOV     EAX,LDivisor.Mid
        MUL     EBX
        ADD     EAX,ESI
        ADC     EDX,0
        MOV     A.L1,EAX
        MOV     EAX,DWORD PTR A
        SUB     DWORD PTR [LDividend+4*ECX],EAX
        MOV     EAX,A.L1
        SBB     DWORD PTR [LDividend+4*ECX+4],EAX
        SBB     DWORD PTR [LDividend+4*ECX+8],EDX

        MOV     DWORD PTR [EDI+4*ECX],EBX

        JNC     @EstimateCorrect64

        DEC     DWORD PTR [EDI+4*ECX]
        MOV     EAX,LDivisor.Lo
        ADD     DWORD PTR [LDividend+4*ECX],EAX
        MOV     EAX,LDivisor.Mid
        ADC     DWORD PTR [LDividend+4*ECX+4],EAX
        ADC     DWORD PTR [LDividend+4*ECX+8],0

@EstimateCorrect64:

        SUB     ECX,1
        JNC     @MainLoop64

@Exit64:

        MOV     EAX,RHat

        POP     EBX
        POP     EDI
        POP     ESI
end;

class function Decimal.Div192by96(Quotient: PLongword; const Dividend, Divisor: Decimal): Longword;
var
  LDivisor: Decimal;
  LDividend: array[0..7] of Longword;
  QHat: TUnsigned64;
  RHat: Longword;
  A: TAccumulator;
  Carry: Longword;
asm

        PUSH    ESI
        PUSH    EDI
        PUSH    EBX

        MOV     EDI,EAX
        MOV     ESI,EDX
        MOV     EBX,ECX

        // Determine number of leading zero bits of divisor and store it in CL.

        MOV     EAX,[EBX].Decimal.Hi
        BSR     ECX,EAX
        SUB     ECX,31
        NEG     ECX
        JE      @NoShift96

        // Shift divisor left by CL and store in LDivisor.

        MOV     EDX,[EBX].Decimal.Mid
        SHLD    EAX,EDX,CL
        MOV     LDivisor.Hi,EAX
        MOV     EAX,[EBX].Decimal.Lo
        SHLD    EDX,EAX,CL
        MOV     LDivisor.Mid,EDX
        SHL     EAX,CL
        MOV     LDivisor.Lo,EAX

        // Shift dividend left by same number of bits (CL) and store in LDividend.

        XOR     EAX,EAX
        MOV     EDX,[ESI].Decimal.Hi
        SHLD    EAX,EDX,CL
        MOV     DWORD PTR [LDividend+28],EAX
        MOV     EAX,[ESI].Decimal.Mid
        SHLD    EDX,EAX,CL
        MOV     DWORD PTR [LDividend+24],EDX
        MOV     EDX,[ESI].Decimal.Lo
        SHLD    EAX,EDX,CL
        MOV     DWORD PTR [LDividend+20],EAX
        SHL     EDX,CL
        MOV     DWORD PTR [LDividend+16],EDX
        JMP     @PrepareMainLoop96

@NoShift96:

        // No shift required, so simply copy.

        MOV     EAX,[EBX].Decimal.Hi
        MOV     LDivisor.Hi,EAX
        MOV     EAX,[EBX].Decimal.Mid
        MOV     LDivisor.Mid,EAX
        MOV     EAX,[EBX].Decimal.Lo
        MOV     LDivisor.Lo,EAX

        MOV     DWORD PTR [LDividend+28],0
        MOV     EAX,[ESI].Decimal.Hi
        MOV     DWORD PTR [LDividend+24],EAX
        MOV     EAX,[ESI].Decimal.Mid
        MOV     DWORD PTR [LDividend+20],EAX
        MOV     EAX,[ESI].Decimal.Lo
        MOV     DWORD PTR [LDividend+16],EAX

@PrepareMainLoop96:

        // Zero out certain parts.

        XOR     EAX,EAX
        MOV     DWORD PTR [LDividend],EAX
        MOV     DWORD PTR [LDividend+4],EAX
        MOV     DWORD PTR [LDividend+8],EAX
        MOV     DWORD PTR [LDividend+12],EAX
        MOV     [EDI].TAccumulator.L0,EAX
        MOV     [EDI].TAccumulator.L1,EAX
        MOV     [EDI].TAccumulator.L2,EAX
        MOV     [EDI].TAccumulator.L3,EAX
        MOV     [EDI].TAccumulator.L4,EAX
        MOV     [EDI].TAccumulator.L5,EAX
        MOV     [EDI].TAccumulator.L6,EAX

        // for i := 3 downto 0 do ...

        MOV     ECX,4

@MainLoop96:

        // Divide top two digits of current part of dividend (N) by top digit of divisor (D) = Qhat
        // IOW: Qhat := N div D;

        XOR     EDX,EDX
        MOV     EBX,LDivisor.Hi
        MOV     EAX,DWORD PTR [LDividend+4*ECX+12]
        DIV     EBX
        MOV     QHat.Hi32,EAX
        MOV     EAX,DWORD PTR [LDividend+4*ECX+8]
        MOV     ESI,EAX
        DIV     EBX
        MOV     QHat.Lo32,EAX

        // Rhat := N - Qhat * D;

        MUL     EBX
        MOV     A.L0,EAX
        MOV     A.L1,EDX
        MOV     EAX,QHat.Hi32
        MUL     EBX
        ADD     EAX,A.L1
        SUB     ESI,A.L0
        MOV     RHat,ESI

@AdjustLoop96:

        CMP     QHat.Hi32,0
        JNE     @DoAdjust96
        MOV     EBX,LDivisor.Mid
        MOV     EAX,QHat.Lo32
        MUL     EBX
        SUB     EAX,DWORD PTR [LDividend+4*ECX+4]
        SBB     EDX,RHat
        JBE     @AdjustLoopEnd96

@DoAdjust96:

        SUB     QHat.Lo32,1
        SBB     QHat.Hi32,0
        MOV     EAX,LDivisor.Hi
        ADD     RHat,EAX
        JZ      @AdjustLoop96

@AdjustLoopEnd96:

        // Finally subtract QHat * divisor from current part of dividend

        MOV     EBX,QHat.Lo32                    // EBX = digit
        MOV     EAX,LDivisor.Lo
        MUL     EBX
        MOV     A.L0,EAX
        MOV     ESI,EDX
        MOV     EAX,LDivisor.Mid
        MUL     EBX
        ADD     EAX,ESI
        ADC     EDX,0
        MOV     A.L1,EAX
        MOV     ESI,EDX
        MOV     EAX,LDivisor.Hi
        MUL     EBX
        ADD     EAX,ESI
        ADC     EDX,0
        MOV     A.L2,EAX                     // A.L2:A.L1:A.L0 = QHat * divisor

        MOV     EAX,A.L0
        SUB     DWORD PTR [LDividend+4*ECX],EAX
        MOV     EAX,A.L1
        SBB     DWORD PTR [LDividend+4*ECX+4],EAX
        MOV     EAX,A.L2
        SBB     DWORD PTR [LDividend+4*ECX+8],EAX
        SBB     DWORD PTR [LDividend+4*ECX+12],EDX

        MOV     DWORD PTR [EDI+4*ECX],EBX

        JNC     @EstimateCorrect96

        DEC     DWORD PTR [EDI+4*ECX]
        MOV     EAX,LDivisor.Lo
        ADD     DWORD PTR [LDividend+4*ECX],EAX
        MOV     EAX,LDivisor.Mid
        ADC     DWORD PTR [LDividend+4*ECX+4],EAX
        MOV     EAX,LDivisor.Hi
        ADC     DWORD PTR [LDividend+4*ECX+8],EAX
        ADC     DWORD PTR [LDividend+4*ECX+12],0

@EstimateCorrect96:

        SUB     ECX,1
        JNC     @MainLoop96

@Exit96:

        MOV     EAX,RHat

        POP     EBX
        POP     EDI
        POP     ESI
end;

// Finds mantissa modulo 5
//
// See Knuth TAOCP, Vol 2, Section 4.3.2, too.
//
// Uses modular arithmetic (well, sort of) to convert finding modulus of 4 24 bit words
// with a few simple additions and multiplications. Uses 24 bit words to avoid values > 32 bit
// and to allow the multiplication with a magic constant instead of a plain division.
//
// Modular arithmetic tells us that:
//
//   (a + b) mod n = ((a mod n) + (b mod n)) mod n
//   (a * b) mod n = ((a mod n) * (b mod n)) mod n
//
// Say we have a 96 bit value X. If we separate X into 24 bit words, the bytes are:
//
//   X = A2:A1:A0:B2:B1:B0:C2:C1:C0:D2:D1:D0
//
// where A2 is the highest and D0 the lowest byte. Let's call the values A, B, C and D.
// To calculate X mod 5, using the rules above, we get:
//
//   X mod 5 = (A * (2^72 mod 5) + B * (2^48 mod 5) + C * (2^24 mod 5) + D) mod 5
//
//   2^72 mod 5 = 1
//   2^48 mod 5 = 1
//   2^24 mod 5 = 1
//
// This finally leads to the formula:
//
//   Result := (A + B + C + D) mod 5
//
// Approx. 2.5x - 3x as fast as using 3x32 bit words and 7x - 8x as fast as a blind division of
// all three longwords in the decimal and taking the remainder!

class function Decimal.Mod96by5(const D: Decimal): Integer;
asm
        PUSH    EBX

        MOV     EBX,[EAX+8]        // EBX = A2:A1:A0:B2
        MOV     ECX,[EAX+4]        // ECX = B1:B0:C2:C1
        MOV     EDX,[EAX]          // EDX = C0:D2:D1:D0

        MOV     EAX,0
        SHLD    EAX,EBX,24         // EAX = A2:A1:A0
        SHLD    EBX,ECX,16         // EBX = B2:B1:B0
        AND     EBX,$00FFFFFF
        SHLD    ECX,EDX,8          // ECX = C2:C1:C0
        AND     ECX,$00FFFFFF
        AND     EDX,$00FFFFFF      // EDX = D2:D1:D0

        ADD     EAX,EBX
        ADD     EAX,ECX            // EAX = A + B + C  (max. $2FFFFFD)
        ADD     EAX,EDX

        MOV     EBX,EAX            // save EAX

        MOV     ECX,$33333334      // magic constant = (2^32+4) div 5, i.e. (2^32+n-1) div n
        MUL     ECX                // EDX = EAX div 5
        LEA     EDX,[EDX+4*EDX]    // EDX = 5 * (EAX div 5)
        MOV     EAX,EBX
        SUB     EAX,EDX            // EAX = EAX - 5 * (EAX div 5) = EAX mod 5

        POP     EBX
end;

class procedure Decimal.InternalDivide(var Result: Decimal; const Left, Right: Decimal);
var
  LResult: ^Decimal;                    // Local copy of Result pointer (which is passed in EAX).
  LDividend: Decimal;                   // Copy of dividend, required for scaling.
  LDivisor: Decimal;                    // Normalized divisor (shifted until "top" bit set).
  LQuotient: TAccumulator;              // Resulting quotient.
  TargetScale: Longword;
asm
        PUSH    EBX
        PUSH    ESI

        MOV     LResult,EAX

        // Need a copy of Left decimal so it can be scaled up. The original should not be touched.

        MOV     ESI,ECX

        MOVZX   EAX,[EDX].Decimal.Scale
        MOVZX   ECX,[ESI].Decimal.Scale
        SUB     EAX,ECX
        JNS     @TargetScaleNotNegative
        MOV     EAX,0

@TargetScaleNotNegative:

        MOV     TargetScale,EAX

        // Look for special cases. Thus a 96 bit division can turn into a 64 bit or 32 bit one.

        MOV     EAX,[EDX].Decimal.Lo
        OR      EAX,[ESI].Decimal.Lo
        JNE     @NoShiftRight

        MOV     EAX,[EDX].Decimal.Mid
        OR      EAX,[ESI].Decimal.Mid
        JE      @ShiftRight64

        // Shift dividend and divisor equally right by 32 bit (1 longword).

        MOV     EAX,[EDX].Decimal.Flags
        MOV     LDividend.Flags,EAX
        MOV     EAX,[EDX].Decimal.Hi
        MOV     LDividend.Mid,EAX
        MOV     EAX,[EDX].Decimal.Mid
        MOV     LDividend.Lo,EAX
        MOV     EAX,[ESI].Decimal.Flags
        MOV     LDivisor.Flags,EAX
        MOV     EAX,[ESI].Decimal.Hi
        MOV     LDivisor.Mid,EAX
        MOV     EAX,[ESI].Decimal.Mid
        MOV     LDivisor.Lo,EAX
        MOV     LDividend.Hi,0
        MOV     LDivisor.Hi,0
        JMP     @ScaleDownDivisor

@ShiftRight64:

        // Shift dividend and divisor equally right by 64 bit (2 longwords).

        MOV     EAX,[EDX].Decimal.Hi
        MOV     LDividend.Lo,EAX
        MOV     EAX,[ESI].Decimal.Hi
        MOV     LDivisor.Lo,EAX
        MOV     EAX,[EDX].Decimal.Flags
        MOV     LDividend.Flags,EAX
        MOV     EAX,[ESI].Decimal.Flags
        MOV     LDivisor.Flags,EAX
        MOV     LDividend.Mid,0
        MOV     LDividend.Hi,0
        MOV     LDivisor.Mid,0
        MOV     LDivisor.Hi,0

@NoShiftRight:

        // Cannot shift dividend or divisor right.

        MOV     EAX,[EDX].Decimal.Lo
        MOV     LDividend.Lo,EAX
        MOV     EAX,[EDX].Decimal.Mid
        MOV     LDividend.Mid,EAX
        MOV     EAX,[EDX].Decimal.Hi
        MOV     LDividend.Hi,EAX
        MOV     EAX,[EDX].Decimal.Flags
        MOV     LDividend.Flags,EAX

        MOV     ECX,[ESI].Decimal.Flags
        MOV     LDivisor.Flags,ECX
        MOV     ECX,[ESI].Decimal.Hi
        MOV     LDivisor.Hi,ECX
        MOV     ECX,[ESI].Decimal.Mid
        MOV     LDivisor.Mid,ECX
        MOV     ECX,[ESI].Decimal.Lo
        MOV     LDivisor.Lo,ECX

        OR      ECX,LDivisor.Mid
        OR      ECX,LDivisor.Hi
        JNE     @ScaleDownDivisor

        MOV     EAX,detZeroDivide
        CALL    Error

@ScaleDownDivisor:

        // If dividend has maximum scale, don't scale down divisor. Must do real division.

        CMP     LDividend.Scale,28
        JE      @ScaleLeftLoop

@ScaleDownDivisorLoop:

        // Divisor is scaled down, so something like 11/100 does not result in a fraction and then
        // in a big number with a high scale, because of the scale-up loop after the division. We
        // calculate 11/1 instead (and an accordingly different scale), so no fraction in
        // L2:L1:L0 and therefore no scale-up required at the end.

        // Can LDivisor be divided by 2?

        TEST    LDivisor.Lo,1
        JNE     @ScaleLeftLoop

        // Can LDivisor be divided by 5?

        LEA     EAX,LDivisor
        CALL    Mod96by5
        OR      EAX,EAX
        JNE     @ScaleLeftLoop

        // Yes, so it can be divided by 10; scale it down.

        LEA     EAX,LDivisor
        CALL    DecScaleDownRaw
        JMP     @ScaleDownDivisorLoop

@ScaleLeft:

        // None of the conditions apply, so scale up.

        LEA    EAX,LDividend
        CALL   DecScaleUpRaw

@ScaleLeftLoop:

        // Scale dividend until mantissa of dividend >= mantissa of divisor, if possible. Do
        // not omit this step, or otherwise 1 / (1 / 3) returns something like 2.903... instead
        // of 3.000...

        // Dividend > divisor ?

        MOV    EAX,LDividend.Lo
        SUB    EAX,LDivisor.Lo
        MOV    EAX,LDividend.Mid
        SBB    EAX,LDivisor.Mid
        MOV    EAX,LDividend.Hi
        SBB    EAX,LDivisor.Hi
        JAE    @ScaleLeftEnd

        // Enough room to scale up?

        CMP    LDividend.Hi,HLWDiv10
        JAE    @ScaleLeftEnd

        // Scale not at maximum yet?

        CMP    LDividend.Scale,28
        JB     @ScaleLeft

@ScaleLeftEnd:

        LEA     EDX,LDividend

        // Result.scale := Left.scale - Right.scale;

        MOV     AL,LDividend.Scale
        SUB     AL,LDivisor.Scale
        MOV     LQuotient.Scale,AL

        // Result.sign := Left.sign xor Right.sign;

        MOV     AL,LDividend.Sign
        XOR     AL,LDivisor.Sign
        MOV     LQuotient.Sign,AL

        // Check size of divisor (Right).

        CMP     LDivisor.Hi,0
        JNE     @Divisor96
        CMP     LDivisor.Mid,0
        JNE     @Divisor64
        MOV     EAX,LDivisor.Lo
        OR      EAX,EAX
        JNE     @Divisor32

        // Divisor zero.

        MOV     EAX,detZeroDivide
        XOR     EDX,EDX
        JMP     Error

@Divisor32:

        // 32 bit divisor.

        LEA     EAX,LQuotient
        MOV     ECX,LDivisor.Lo
        CALL    Div192by32
        JMP     @ScaleLoop

@Divisor64:

        // 64 bit divisor.

        LEA     EAX,LQuotient
        LEA     ECX,LDivisor
        CALL    Div192by64
        JMP     @ScaleLoop

@Divisor96:

        // 96 bit divisor.

        LEA     EAX,LQuotient
        LEA     ECX,LDivisor
        CALL    Div192by96

        // Scale up to remove fraction as much as possible.

@ScaleLoop:

        // Negative scale? Must scale up until it is 0.

        MOVSX   EDX,LQuotient.Scale
        CMP     EDX,0
        JGE     @ScaleNotNegative
        NEG     EDX
        JMP     @DoScale

@MustScaleDown:

        // Scale > 28, so must scale down!

        NEG     EDX
        LEA     EAX,LQuotient
        CALL    AccuScaleDownMax

        CMP     LQuotient.Underflow,$80000000
        JA      @MustRound
        JB      @MustNotRound
        TEST    LQuotient.L0,1
        JZ      @MustNotRound

@MustRound:

        ADD     LQuotient.L0,1
        ADC     LQuotient.L1,0
        ADC     LQuotient.L2,0
        ADC     LQuotient.L3,0
        ADC     LQuotient.L4,0
        ADC     LQuotient.L5,0

@MustNotRound:

        // Check if scale <= 28 now.

        MOV     EDX,28
        MOVSX   EAX,LQuotient.Scale
        SUB     EDX,EAX
        JS      @MustScaleDown
        JE      @Scaled

@ScaleNotNegative:

        // Check if accu can be scaled up.

        CMP     LQuotient.L6,HLWDiv10
        JAE     @Scaled

        // Check if LQuotient.scale > 28 and scale down if so.

        MOV     EDX,28
        MOVSX   EAX,LQuotient.Scale
        SUB     EDX,EAX
        JS      @MustScaleDown

        // if LQuotient.scale = 28, it can't be scaled up anymore.

        JE      @Scaled

        // if factional part = 0, no need to scale anymore.

        MOV     EAX,LQuotient.L0
        OR      EAX,LQuotient.L1
        OR      EAX,LQuotient.L2
        OR      EAX,LQuotient.L3
        JE      @Scaled

@DoScale:

        // Scale up by EDX

        LEA     EAX,LQuotient
        CALL    AccuScaleUpMax224
        OR      EAX,EAX
        JE      @ScaleLoop

@Scaled:

        // Quotient is scaled or can't be scaled anymore.
        // If scale < 0, there must be an overflow situation.

        CMP     LQuotient.Scale,0
        JGE     @NoOverflow

        MOV     EAX,detOverflow
        CALL    Error

@NoOverflow:

        // Check if quotient must be rounded.

        // Banker's rounding:
        // if > half digit, scale up
        // if < half digit, scale down
        // if = half digit, scale to nearest even.

        MOV     EAX,LQuotient.L3
        CMP     EAX,$80000000
        JB      @NoRound
        JA      @DoRound
        TEST    LQuotient.L4,1
        JE      @NoRound

@DoRound:

        MOV     EAX,LQuotient.L4
        AND     EAX,LQuotient.L5
        AND     EAX,LQuotient.L6
        CMP     EAX,$FFFFFFFF
        JE      @NoRound
        ADD     LQuotient.L4,1
        ADC     LQuotient.L5,0
        ADC     LQuotient.L6,0

@NoRound:

        // Copy top 3 longwords and flags of quotient to Result.

        MOV     EDX,LResult
        MOV     EAX,LQuotient.L4
        MOV     [EDX].Decimal.Lo,EAX
        MOV     EAX,LQuotient.L5
        MOV     [EDX].Decimal.Mid,EAX
        MOV     EAX,LQuotient.L6
        MOV     [EDX].Decimal.Hi,EAX
        MOV     EAX,LQuotient.Flags
        MOV     [EDX].Decimal.Flags,EAX
        MOV     [EDX].Decimal.Reserved,0

        // Try to eliminate trailing zero digits, if scale > target scale

        // This is not without cost, but required to get correct result, until I find
        // a better way to achieve this (e.g. not scaling up further than necessary).

        MOV     EAX,[EDX].Lo
        OR      EAX,[EDX].Mid
        OR      EAX,[EDX].Hi
        JE      @Exit
        MOV     EAX,EDX
        MOV     EDX,TargetScale
        CALL    DecScaleDownTo

        // Think about this:
        //
        // It is possible to find the maximum number that an accu or decimal can be scaled up
        // by counting the leading zero bits (up to 95). This is in fact the 2log. Multiplying
        // that number by 10log(2) = 0.30103 then gives a 10log.
        // This can be approximated by e.g. 1233/4096 (* 1233, shr 12) = 0.301025, or
        // 5050445/1677726 (* 5050445, shr 24) = 0.301029, although it can be one too low, so
        // after that, a test against HLWdiv10 is required and another scale up may be necessary.
        //
        // The lowest bit set of an accu or decimal is significant in two ways:
        // 1. the number of trailing zeroes determines how far the decimal or accu can be
        //    scaled down maximally without losing information, since 10 = 5 * 2. Mod96by5 can
        //    help to determine if it can also be divided by 5.
        // 2. the lowest bit (of the fractional part) determines, how many times an accu must be
        //    scaled up to clear the fractional part.
        //
        // Not sure how this can be used in the scaling part above.

@Exit:

        POP     ESI
        POP     EBX
end;

class procedure Decimal.InternalFloor(var Result: Decimal; const Source: Decimal);
asm
        PUSH    EDI

        MOV     EDI,EAX
        CALL    InternalTruncate
        CMP     [EDI].Decimal.Sign,0
        JE      @Exit
        ADD     [EDI].Decimal.Lo,1
        ADC     [EDI].Decimal.Mid,0
        ADC     [EDI].Decimal.Hi,0

@Exit:

        POP     EDI
end;

// Basecase multiplication
//
// Karatsuba, Divide and Conquer, etc. are probably only useful for larger sizes.

class procedure Decimal.InternalMultiply(var Result: Decimal;
  const Left, Right: Decimal);
var
  A: TAccumulator;
  LResult: Longword;
asm
        PUSH    ESI
        PUSH    EDI
        PUSH    EBX

        MOV     LResult,EAX

        MOV     ESI,EDX
        MOV     EDI,ECX
        MOV     ECX,EAX
        XOR     EAX,EAX
        MOV     A.L0,EAX
        MOV     A.L1,EAX
        MOV     A.L2,EAX
        MOV     A.L3,EAX
        MOV     A.L4,EAX
        MOV     A.L5,EAX
        MOV     A.Underflow,EAX
        MOV     A.Flags,EAX

        // Add exponents

        MOV     AL,[ESI].Decimal.Scale
        ADD     AL,[EDI].Decimal.Scale
        MOV     A.Scale,AL

        // Set sign

        MOV     AL,[ESI].Decimal.Sign
        XOR     AL,[EDI].Decimal.Sign
        MOV     A.Sign,AL

{                                                             }
{                         [ RH ][ RM ][ RL ]                  }
{                         [ LH ][ LM ][ LL ]                  }
{                         ------------------ x                }
{                               [   LL*RL  ]                  }
{                         [   LL*RM  ]   |                    }
{                   [   LL*RH  ]   |     |                    }
{                         [   LM*RL  ]   |                    }
{                   [   LM*RM  ]   |     |                    }
{             [   LM*RH  ]   |     |     |                    }
{                   [   LH*RL  ]   |     |                    }
{             [   LH*RM  ]   |     |     |                    }
{       [   LH*RH  ]         |     |     |                    }
{       ---v-----v-----v-----v-----v-----v-- +                }
{       [ A5 ][ A4 ][ A3 ][ A2 ][ A1 ][ A0 ]                  }
{                                                             }

        MOV     EBX,[ESI].Decimal.Lo         // ESI:ECX:EBX is mantissa of Left.
        OR      EBX,EBX
        JE      @LeftMid

        // Left.Lo * Right.Lo

        MOV     EAX,[EDI].Decimal.Lo
        MUL     EBX
        MOV     A.L0,EAX
        MOV     A.L1,EDX

        // Left.Lo * Right.Mid

        MOV     EAX,[EDI].Decimal.Mid
        MUL     EBX
        ADD     A.L1,EAX
        ADC     A.L2,EDX

        // Left.Lo * Right.Hi

        MOV     EAX,[EDI].Decimal.Hi
        MUL     EBX
        ADD     A.L2,EAX
        ADC     A.L3,EDX

@LeftMid:

        MOV     EBX,[ESI].Decimal.Mid
        OR      EBX,EBX
        JE      @LeftHi

        // Left.Mid * Right.Lo

        MOV     EAX,[EDI].Decimal.Lo
        MUL     EBX
        ADD     A.L1,EAX
        ADC     A.L2,EDX
        ADC     A.L3,0

        // Left.Mid * Right.Mid

        MOV     EAX,[EDI].Decimal.Mid
        MUL     EBX
        ADD     A.L2,EAX
        ADC     A.L3,EDX
        ADC     A.L4,0

        // Left.Mid * Right.Hi

        MOV     EAX,[EDI].Decimal.Hi
        MUL     EBX
        ADD     A.L3,EAX
        ADC     A.L4,EDX
        ADC     A.L5,0

@LeftHi:

        MOV     EBX,[ESI].Decimal.Hi
        OR      EBX,EBX
        JE      @CheckTop

        // Left.Hi * Right.Lo

        MOV     EAX,[EDI].Decimal.Lo
        MUL     EBX
        ADD     A.L2,EAX
        ADC     A.L3,EDX
        ADC     A.L4,0

        // Left.Hi * Right.Mid

        MOV     EAX,[EDI].Decimal.Mid
        MUL     EBX
        ADD     A.L3,EAX
        ADC     A.L4,EDX
        ADC     A.L5,0

        // Left.Hi * Right.Hi

        MOV     EAX,[EDI].Decimal.Hi
        MUL     EBX
        ADD     A.L4,EAX
        ADC     A.L5,EDX

@CheckTop:

        MOV     AL,A.Scale
        SUB     AL,28
        JLE     @CheckL5
        MOVZX   EDX,AL
        LEA     EAX,A
        CALL    AccuScaleDownMax
        JMP     @CheckTop

@CheckL5:

        CMP     A.L5,0
        JE      @CheckL4
        CMP     A.Scale,0
        JE      @Overflow
        LEA     EAX,A
        MOV     EDX,9
        CALL    AccuScaleDownMax
        JMP     @CheckL5

@CheckL4:

        CMP     A.L4,0
        JE      @CheckL3
        CMP     A.Scale,0
        JE      @Overflow
        LEA     EAX,A
        MOV     EDX,9
        CALL    AccuScaleDownMax
        JMP     @CheckL4

@CheckL3:

        // Loop fully unrolled, for speed:

        MOV     ECX,A.L3
        CMP     ECX,0
        JE      @Exit
        CMP     A.Scale,0
        JE      @Overflow
        LEA     EAX,A
        CMP     ECX,10
        JNB     @Not10
        CALL    AccuScaleDownRaw128
        JMP     @Exit

@Not10:

        CMP     ECX,100
        JNB     @Not100
        MOV     EDX,2
        CALL    AccuScaleDownMax128
        JMP     @Exit

@Not100:

        CMP     ECX,1000
        JNB     @Not1000
        MOV     EDX,3
        CALL    AccuScaleDownMax128
        JMP     @Exit

@Not1000:

        CMP     ECX,10*1000
        JNB     @Not10000
        MOV     EDX,4
        CALL    AccuScaleDownMax128
        JMP     @Exit

@Not10000:

        CMP     ECX,100*1000
        JNB     @Not100000
        MOV     EDX,5
        CALL    AccuScaleDownMax128
        JMP     @Exit

@Not100000:

        CMP     ECX,1000*1000
        JNB     @Not1000000
        MOV     EDX,6
        CALL    AccuScaleDownMax128
        JMP     @Exit

@Not1000000:

        CMP     ECX,10*1000*1000
        JNB     @Not10000000
        MOV     EDX,7
        CALL    AccuScaleDownMax128
        JMP     @Exit

@Not10000000:

        CMP     ECX,100*1000*1000
        JNB     @Not100000000
        MOV     EDX,8
        CALL    AccuScaleDownMax128
        JMP     @Exit

@Not100000000:

        MOV     EDX,9
        CALL    AccuScaleDownMax128
        JMP     @CheckL3

@Exit:

        CMP     A.Underflow,$80000000
        JB      @NoRound
        JA      @DoRound
        TEST    A.L0,1
        JZ      @NoRound

@DoRound:

        ADD     A.L0,1
        ADC     A.L1,0
        ADC     A.L2,0
        ADC     A.L3,0

@NoRound:

        CMP     A.L3,0
        JZ      @NoOverflow

        CMP     A.Scale,0
        JLE     @Overflow

        LEA     EAX,A
        CALL    AccuScaleDownRaw128
        JMP     @NoOverflow

@Overflow:

        MOV     EAX,detOverflow
        XOR     EDX,EDX
        JMP     Error

@NoOverflow:

        CMP     A.Scale,0
        JL      @Overflow

        MOV     EDI,LResult
        MOV     EAX,A.L0
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EAX,A.L1
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,A.L2
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,A.Flags
        MOV     [EDI].Decimal.Flags,EAX

        POP     EBX
        POP     EDI
        POP     ESI
end;

class procedure Decimal.InternalRound(var Result: Decimal; const Source: Decimal);
asm
        PUSH    EDI

        MOV     EDI,EAX
        MOV     EAX,[EDX].Decimal.Lo
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EAX,[EDX].Decimal.Mid
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,[EDX].Decimal.Hi
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,[EDX].Decimal.Flags
        MOV     [EDI].Decimal.Flags,EAX

@Loop:

        CMP     [EDI].Decimal.Scale,0
        JLE     @EndLoop
        MOV     EAX,EDI
        MOVZX   EDX,[EDI].Decimal.Scale
        CALL    DecScaleDownMaxRaw
        JMP     @Loop

@EndLoop:

        POP     EDI
end;

class procedure Decimal.InternalSubtract(var Result: Decimal;
  const Left, Right: Decimal);
begin
  InternalAddSub(Result, Left, Right, $80);
end;

class procedure Decimal.InternalToDouble(var Result: Double; const Source: Decimal);
var
  A: TAccumulator;
  LResult: ^Decimal;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        MOV     LResult,EAX

        XOR     EAX,EAX
        MOV     ESI,EDX
        XOR     EAX,EAX
        MOV     A.L0,EAX
        MOV     A.L1,EAX
        MOV     A.L2,EAX
        MOV     EAX,[ESI].Decimal.Lo
        MOV     A.L3,EAX
        MOV     EAX,[ESI].Decimal.Mid
        MOV     A.L4,EAX
        MOV     EAX,[ESI].Decimal.Hi
        MOV     A.L5,EAX
        MOV     EAX,[ESI].Decimal.Flags
        MOV     A.Flags,EAX

@ScaleLoop:

        CMP     A.Scale,0
        JE      @Scaled
        LEA     EAX,A
        CALL    AccuScaleDownRaw
        JMP     @ScaleLoop

@Scaled:

        MOV     EDX,5

@FindLoop:

        CMP     DWORD PTR [A+4*EDX],0
        JNE     @FoundTop
        DEC     EDX
        JNS     @FindLoop
        JMP     @IsZero

@FoundTop:

        MOV     ESI,DWORD PTR [A+4*EDX]  // ESI:EDI:EBX contain significant bits
        CMP     EDX,0
        JE      @NoLower0
        MOV     EDI,DWORD PTR [A+4*EDX-4]
        CMP     EDX,1
        JE      @NoLower1
        MOV     EBX,DWORD PTR [A+4*EDX-8]
        JMP     @FindBit

@NoLower0:

        XOR     EDI,EDI

@NoLower1:

        XOR     EBX,EBX

@FindBit:

        BSR     EAX,ESI
        JZ      @IsZero
        SHL     EDX,5
        OR      EDX,EAX                  // EDX = exponent
        ADD     EDX,DoubleBias-96
        MOV     ECX,EAX
        NEG     ECX
        JZ      @ZeroShift
        SHLD    ESI,EDI,CL
        SHLD    EDI,EBX,CL
        JMP     @Shifted

@ZeroShift:

        MOV     ESI,EDI
        MOV     EDI,EBX

@Shifted:

        MOV     EAX,EDI                  // ESI:EDI = mantissa shifted fully left
        TEST    EAX,$0800
        JE      @NoRound
        ADD     EAX,$1000
        ADC     ESI,0
        ADC     EDX,0

@NoRound:

        SHRD    EAX,ESI,12               // ESI:EAX = mantissa
        SHR     ESI,12
        AND     EDX,DoubleEMask
        SHL     EDX,52-32
        OR      EDX,ESI
        MOV     ESI,A.Flags
        AND     ESI,$80000000
        OR      EDX,ESI
        JMP     @Finish

@IsZero:

        XOR     EAX,EAX
        XOR     EDX,EDX

@Finish:

        MOV     EDI,LResult
        MOV     [EDI],EAX
        MOV     [EDI+4],EDX

        POP     EBX
        POP     ESI
        POP     EDI
end;

class procedure Decimal.InternalToExtended(var Result: Extended;
  const Source: Decimal);
var
  A: TAccumulator;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        PUSH    EAX

        XOR     EAX,EAX
        MOV     ESI,EDX
        XOR     EAX,EAX
        MOV     A.L0,EAX
        MOV     A.L1,EAX
        MOV     A.L2,EAX
        MOV     EAX,[ESI].Decimal.Lo
        MOV     A.L3,EAX
        MOV     EAX,[ESI].Decimal.Mid
        MOV     A.L4,EAX
        MOV     EAX,[ESI].Decimal.Hi
        MOV     A.L5,EAX
        MOV     EAX,[ESI].Decimal.Flags
        MOV     A.Flags,EAX
@ScaleLoop:
        CMP     A.Scale,0
        JE      @Scaled
        LEA     EAX,A
        CALL    AccuScaleDownRaw
        JMP     @ScaleLoop
@Scaled:
        MOV     EDX,5
@FindLoop:
        CMP     DWORD PTR [A+4*EDX],0
        JNE     @FoundTop
        DEC     EDX
        JNS     @FindLoop
        JMP     @IsZero
@FoundTop:
        MOV     ESI,DWORD PTR [A+4*EDX]  // ESI:EDI:EBX contain significant bits
        CMP     EDX,0
        JE      @NoLower0
        MOV     EDI,DWORD PTR [A+4*EDX-4]
        CMP     EDX,1
        JE      @NoLower1
        MOV     EBX,DWORD PTR [A+4*EDX-8]
        JMP     @FindBit
@NoLower0:
        XOR     EDI,EDI
@NoLower1:
        XOR     EBX,EBX
@FindBit:
        BSR     EAX,ESI
        JZ      @IsZero
        SHL     EDX,5
        OR      EDX,EAX
        ADD     EDX,ExtendedBias-96      // EDX = exponent

        MOV     ECX,31
        SUB     ECX,EAX
        JZ      @Shifted
        SHLD    ESI,EDI,CL
        SHLD    EDI,EBX,CL
        SHL     EBX,CL
        JMP     @Shifted
@Shifted:
        MOV     EAX,EDI
        CMP     EBX,$80000000
        JB      @NoRound
        ADD     EAX,1
        ADC     ESI,0
        JNC     @NoRound
        ADC     EDX,0
        STC
        RCR     ESI,1
        RCR     EAX,1
@NoRound:
        XOR     ECX,ECX
        MOV     CH,A.Sign
        AND     EDX,$7FFF
        OR      ECX,EDX
        MOV     EDX,ESI
        JMP     @Finish
@IsZero:
        XOR     EAX,EAX
        XOR     EDX,EDX
        XOR     ECX,ECX
@Finish:
        POP     EDI
        MOV     [EDI],EAX
        MOV     [EDI+4],EDX
        MOV     [EDI+8],CX

        POP     EBX
        POP     ESI
        POP     EDI
end;

class procedure Decimal.InternalToSingle(var Result: Single; const Source: Decimal);
var
  A: TAccumulator;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EAX

        XOR     EAX,EAX
        MOV     ESI,EDX
        MOV     A.L0,EAX
        MOV     A.L1,EAX
        MOV     A.L2,EAX
        MOV     EAX,[EDX].Decimal.Lo
        MOV     A.L3,EAX
        MOV     EAX,[EDX].Decimal.Mid
        MOV     A.L4,EAX
        MOV     EAX,[EDX].Decimal.Hi
        MOV     A.L5,EAX
        MOV     EAX,[EDX].Decimal.Flags
        MOV     A.Flags,EAX

@ScaleLoop:

        CMP     A.Scale,0
        JE      @Scaled
        LEA     EAX,A
        CALL    AccuScaleDownRaw
        JMP     @ScaleLoop

@Scaled:

        MOV     EDX,5

@FindLoop:

        CMP     DWORD PTR [A+4*EDX],0
        JNE     @FoundTop
        DEC     EDX
        JNS     @FindLoop
        JMP     @IsZero

@FoundTop:

        MOV     ESI,DWORD PTR [A+4*EDX]
        CMP     EDX,0
        JE      @NoLower
        MOV     EDI,DWORD PTR [A+4*EDX-4]
        JMP     @FindBit

@NoLower:

        XOR     EDI,EDI

@FindBit:

        BSR     EAX,ESI
        JZ      @IsZero
        SHL     EDX,5
        OR      EDX,EAX                  // EDX = exponent
        ADD     EDX,SingleBias-96
        MOV     ECX,EAX
        NEG     ECX
        JZ      @ZeroShift
        SHLD    ESI,EDI,CL
        JMP     @Shifted

@ZeroShift:

        MOV     ESI,EDI

@Shifted:

        MOV     EAX,ESI                  // ESI = mantissa
        TEST    EAX,$100
        JE      @NoRound
        ADD     EAX,$200
        ADC     EDX,0

@NoRound:

        SHR     EAX,9
        AND     EDX,$FF
        SHL     EDX,23
        OR      EAX,EDX
        MOV     EDX,A.Flags
        AND     EDX,$80000000
        OR      EAX,EDX
        JMP     @Finish

@IsZero:

        XOR     EAX,EAX

@Finish:

        POP     EDI
        MOV     [EDI],EAX

        POP     ESI
        POP     EDI
end;

class procedure Decimal.InternalTruncate(var Result: Decimal;
  const Source: Decimal);
asm
        PUSH    EDI
        PUSH    ESI

        MOV     ESI,EDX
        MOV     EDI,EAX
        MOV     EAX,[EDX].Decimal.Lo
        MOV     [EDI].Decimal.Lo,EAX
        MOV     EAX,[EDX].Decimal.Mid
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,[EDX].Decimal.Hi
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,[EDX].Decimal.Flags
        MOV     [EDI].Decimal.Flags,EAX

@ScaleDownLoop:

        MOVZX   EDX,[EDI].Decimal.Scale
        OR      EDX,EDX
        JLE     @Endloop
        CMP     EDX,9
        JLE     @ScaleOK
        MOV     EDX,9

@ScaleOK:

        SUB     [EDI].Decimal.Scale,DL
        MOV     ECX,DWORD PTR [PowersOfTen+4*EDX]
        XOR     EDX,EDX
        MOV     EAX,[EDI].Decimal.Hi
        DIV     ECX
        MOV     [EDI].Decimal.Hi,EAX
        MOV     EAX,[EDI].Decimal.Mid
        DIV     ECX
        MOV     [EDI].Decimal.Mid,EAX
        MOV     EAX,[EDI].Decimal.Lo
        DIV     ECX
        MOV     [EDI].Decimal.Lo,EAX
        JMP     @ScaleDownLoop

@EndLoop:

        POP     ESI
        POP     EDI
end;

function Decimal.IsNegative: Boolean;
begin
  Result := Sign <> 0;
end;

function Decimal.IsZero: Boolean;
begin
  Result := (Lo or Mid or Hi) = 0;
end;

class operator Decimal.LessThan(const Left, Right: Decimal): Boolean;
begin
  Result := Compare(Left, Right) < 0;
end;

class operator Decimal.LessThanOrEqual(const Left, Right: Decimal): Boolean;
begin
  Result := Compare(Left, Right) <= 0;
end;

class function Decimal.Multiply(const Left, Right: Decimal): Decimal;
begin
  InternalMultiply(Result, Left, Right);
end;

class operator Decimal.Multiply(const Left, Right: Decimal): Decimal;
begin
  InternalMultiply(Result, Left, Right);
end;

class operator Decimal.Modulus(const Dividend, Divisor: Decimal): Decimal;
var
  d1, d2, d4: Decimal;

begin
  d1 := Dividend;
  d2 := Divisor;

  d2.Sign := d1.Sign;
  if Decimal.Abs(d1) < Decimal.Abs(d2) then
    Exit(d1);
  d1 := d1 - d2;
  if d1 = Decimal.Zero then
    d1.Sign := d2.Sign;
  d4 := d1 - Truncate(d1 / d2) * d2;
  if d1.Sign = d4.Sign then
    Exit(d4);
  if d4 = Decimal.Zero then
  begin
    d4.Sign := d1.Sign;
    Exit(d4);
  end;
  Exit(d4 + d2);

//  Result := Dividend - Trunc(Dividend / Divisor) * Divisor;
end;

class function Decimal.Negate(const D: Decimal): Decimal;
begin
  Result := D;
  Result.Sign := D.Sign xor $80;
end;

class operator Decimal.Negative(const D: Decimal): Decimal;
begin
  Result := D;
  Result.Sign := D.Sign xor $80;
end;

class operator Decimal.NotEqual(const Left, Right: Decimal): Boolean;
begin
  Result := Compare(Left, Right) <> 0;
end;

class function Decimal.Parse(const S: string; Settings: TFormatSettings): Decimal;
begin
  if not TryParse(S, Settings, Result) then
    Error(detParse, S);
end;

class function Decimal.Parse(const S: string): Decimal;
begin
  if not TryParse(S, Result) then
    Error(detParse, S);
end;

class operator Decimal.Positive(const D: Decimal): Decimal;
begin
  Result := D;
end;

class function Decimal.PowerOfTen(Power: Shortint): Decimal;
begin
  if (Power < -28) or (Power > 28) then
    Error(detInvalidArg, 'power');
  Result := DecimalPowersOfTen[Power];
end;

class function Decimal.Reciprocal(const D: Decimal): Decimal;
begin
  Result := Decimal.One / D;
end;

class function Decimal.Remainder(const Left, Right: Decimal): Decimal;
begin
  Result := Left mod Right;
end;

class function Decimal.Round(const D: Decimal): Decimal;
begin
  InternalRound(Result, D);
end;

class operator Decimal.Round(const D: Decimal): Decimal;
begin
  InternalRound(Result, D);
end;

class function Decimal.RoundTo(const D: Decimal; Digits: Integer): Decimal;
begin
  if (Digits < -28) or (Digits > 28) then
    Error(detInvalidArg, 'digits');
  Result := D;
  if Digits >= 0 then
  begin
    Result := D;
    while Result.Scale > Digits do
      DecScaleDownMaxRaw(Result, Result.Scale - Digits);
    while (Result.Scale < Digits) and DecScaleUpMax(Result, Digits - Result.Scale) do
      ;
  end
  else
  begin
    InternalMultiply(Result, D, DecimalPowersOfTen[Digits]);
    InternalRound(Result, Result);
    InternalMultiply(Result, Result, DecimalPowersOfTen[-Digits]);
  end;
end;

class function Decimal.Sqr(const D: Decimal): Decimal;
begin
  Result := D * D;
end;

class operator Decimal.Subtract(const Left, Right: Decimal): Decimal;
begin
  InternalSubtract(Result, Left, Right);
end;

class function Decimal.Subtract(const Left, Right: Decimal): Decimal;
begin
  InternalSubtract(Result, Left, Right);
end;

function Decimal.ToCurrency: Currency;
var
  D: Decimal;
begin
  try
    D := Decimal.RoundTo(Self, 4);
  except
    Error(detConversion, 'Currency');
  end;
  if (D.Hi or (D.Mid and $80000000)) <> 0 then
    Error(detConversion, 'Currency');
  PLongword(@Result)[0] := D.Lo;
  PLongword(@Result)[1] := D.Mid;
  if Sign <> 0 then
    Result := -Result;
end;

function Decimal.ToDouble: Double;
begin
  InternalToDouble(Result, Self);
end;

function Decimal.ToExtended: Extended;
begin
  InternalToExtended(Result, Self);
end;

function Decimal.ToInt64: Int64;
var
  D: Decimal;
begin
  D := Decimal.Round(Self);
  if (D.Hi or (D.Mid and $80000000)) <> 0 then
    Error(detConversion, 'Int64');
  PLongword(@Result)[0] := D.Lo;
  PLongword(@Result)[1] := D.Mid;
  if Sign <> 0 then
    Result := -Result;
end;

function Decimal.ToLongint: Longint;
var
  D: Decimal;
begin
  D := Decimal.Round(Self);
  if (D.Hi or D.Mid or (D.Lo and $80000000)) <> 0 then
    Error(detConversion, 'Longint');
  Longword(Result) := D.Lo;
  if Sign <> 0 then
    Result := -Result;
end;

function Decimal.ToLongword: Longword;
var
  D: Decimal;
begin
  D := Decimal.Round(Self);
  if (D.Hi or D.Mid) <> 0 then
    Error(detConversion, 'Longword');
  Longword(Result) := D.Lo;
end;

procedure StoreChar(var P: PChar; C: Char); inline;
begin
  P^ := C;
  Dec(P);
end;

(*
Format string output:

Format string                       Output                                          Type
----------------------------------- ----------------------------------------------- ----------------------
<none>                              123456789,123456789                             unformatted
<empty>                             123456789,123456789                             global (G)
C:                                  123.456.789,12 ?                                currency (fff.fff.fff,ff $)
C20:                                123.456.789,12345678900000000000 ?              currency 20 decimals
E:                                  1,234568E+008                                   exponential (1,ffffffE+00n)
E40:                                1,2345678912345678900000000000000000000000E+008 exponential 40 characters
F:                                  123456789,12                                    fixed point (fffff,ff)
F10:                                123456789,1234567890                            fixed point 10 decimals
G:                                  123456789,123456789                             general
G10:                                123456789,1                                     general 10 characters
N:                                  123.456.789,12                                  number (2 decimals)
N10:                                123.456.789,1234567890                          number 10 decimals
P:                                  12.345.678.912,35%                              percent (2 decimals)
P10:                                12.345.678.912,3456789000%                      percent 10 decimals
Cuckoo 0000000000.000000000000000:  Cuckoo 0123456789,123456789000000
Cuckoo 0,00000000.000000000000000:  Cuckoo 123.456.789,123456789000000
Cuckoo 0000000000,000000000000000:  Cuckoo 0.000.000.000.000.000.123.456.789
Cuckoo 0000000000,000000000.000000: Cuckoo 0.000.000.000.123.456.789,123457

See:
ms-help://MS.NETFramework.v20.en/dv_fxfund/html/580e57eb-ac47-4ffd-bccd-3a1637c2f467.htm (standard)
ms-help://MS.NETFramework.v20.en/dv_fxfund/html/6f74fd32-6c6b-48ed-8241-3c2b86dea5f4.htm (custom)
ms-help://MS.NETFramework.v20.en/dv_fxfund/html/33f27256-722f-4296-a969-3a07dd4f2a02.htm (examples of custom)
*)

function Decimal.ToString(Format: string): string;
begin
  // TODO: implement
end;

function Decimal.ToString(const Settings: TFormatSettings): string;
var
  Buffer: array[0..50] of Char;
  D: Decimal;
  DecimalSeparator: Char;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        PUSH    ECX             // Result string

        MOV     EBX,EAX
        MOV     ESI,EAX
        MOV     AX,[EDX].TFormatSettings.DecimalSeparator
        MOV     DecimalSeparator,AX
        MOV     EAX,[ESI].Decimal.Lo
        MOV     D.Lo,EAX
        MOV     EAX,[ESI].Decimal.Mid
        MOV     D.Mid,EAX
        MOV     EAX,[ESI].Decimal.Hi
        MOV     D.Hi,EAX
        MOV     EAX,[ESI].Decimal.Flags
        MOV     D.Flags,EAX

        LEA     ESI,[Buffer+2*50]
        XOR     EDI,EDI

        MOV     WORD PTR [ESI],0
        SUB     ESI,2
        INC     EDI

@FractionLoop:

        CMP     D.Scale,0
        JLE     @EndFractionLoop
        LEA     EAX,[D]
        CALL    DecScaleDownRaw
        ADD     AL,'0'
        MOV     AH,0
        MOV     [ESI],AX
        SUB     ESI,2
        INC     EDI
        JMP     @FractionLoop

@EndFractionLoop:

        CMP     [EBX].Decimal.Scale,0
        JE      @IntegerLoop
        MOV     AX,DecimalSeparator
        MOV     [ESI],AX
        SUB     ESI,2
        INC     EDI

@IntegerLoop:

        LEA     EAX,[D]
        CALL    DecScaleDownRaw
        ADD     AL,'0'
        MOV     AH,0
        MOV     [ESI],AX
        SUB     ESI,2
        INC     EDI
        MOV     EAX,D.Lo
        OR      EAX,D.Mid
        OR      EAX,D.Hi
        JNE     @IntegerLoop
        CMP     D.Sign,0
        JE      @Exit
        MOV     WORD PTR [ESI],'-'
        SUB     ESI,2
        INC     EDI

@Exit:

        POP     EAX             // Result string
        MOV     EDX,ESI
        ADD     EDX,2           // PChar to string in buffer
        MOV     ECX,EDI         // Length of string
        CALL    System.@UStrFromPWCharLen

        POP     EBX
        POP     ESI
        POP     EDI
end;

function Decimal.ToString(Format: string; const Settings: TFormatSettings): string;
begin
  // TODO: implement
end;

function Decimal.ToSingle: Single;
begin
  InternalToSingle(Result, Self);
end;

function Decimal.ToString: string;
var
  Buffer: array[0..50] of Char;
  D: Decimal;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        PUSH    EDX

        MOV     EBX,EAX
        MOV     EAX,[EBX].Decimal.Lo
        MOV     D.Lo,EAX
        MOV     EAX,[EBX].Decimal.Mid
        MOV     D.Mid,EAX
        MOV     EAX,[EBX].Decimal.Hi
        MOV     D.Hi,EAX
        MOV     EAX,[EBX].Decimal.Flags
        MOV     D.Flags,EAX
        LEA     ESI,[Buffer+50*2]
        XOR     EDI,EDI

        MOV     WORD PTR [ESI],0
        SUB     ESI,2

@FractionLoop:

        CMP     D.Scale,0
        JLE     @EndFractionLoop
        LEA     EAX,[D]
        CALL    DecScaleDownRaw
        ADD     AL,'0'
        MOV     AH,0
        MOV     [ESI],AX
        SUB     ESI,2
        INC     EDI
        JMP     @FractionLoop

@EndFractionLoop:

        CMP     [EBX].Decimal.Scale,0
        JE      @IntegerLoop
        MOV     AX,TFormatSettings.DecimalSeparator
        MOV     [ESI],AX
        SUB     ESI,2
        INC     EDI

@IntegerLoop:

        LEA     EAX,[D]
        CALL    DecScaleDownRaw
        ADD     AL,'0'
        MOV     AH,0
        MOV     [ESI],AX
        SUB     ESI,2
        INC     EDI
        MOV     EAX,D.Lo
        OR      EAX,D.Mid
        OR      EAX,D.Hi
        JNE     @IntegerLoop
        CMP     D.Sign,0
        JE      @Exit
        MOV     WORD PTR [ESI],'-'
        SUB     ESI,2
        INC     EDI

@Exit:

        POP     EAX             // Result string
        MOV     EDX,ESI
        ADD     EDX,2           // PChar to string in buffer
        MOV     ECX,EDI         // Length of string
        CALL    System.@UStrFromPWCharLen

        POP     EBX
        POP     ESI
        POP     EDI
end;

function Decimal.ToUInt64: UInt64;
var
  D: Decimal;
begin
  D := Decimal.Round(Self);
  if D.Hi <> 0 then
    Error(detConversion, 'UInt64');
  PLongword(@Result)[0] := D.Lo;
  PLongword(@Result)[1] := D.Mid;
end;

class operator Decimal.Trunc(const D: Decimal): Decimal;
begin
  InternalTruncate(Result, D);
end;

class function Decimal.Truncate(const D: Decimal): Decimal;
begin
  InternalTruncate(Result, D);
end;

// Tries to parse S as Decimal. Returns True if successful.
class function Decimal.TryParse(const S: string;
  const Settings: TFormatSettings; out Res: Decimal): Boolean;
var
  InExponent: Boolean;
  IsNegative: Boolean;
  IsNegativeExponent: Boolean;
  Exponent: Integer;
  NumDecimals: Integer;
  DecimalPointPos: PChar;
  P: PChar;
  DecimalSep: Char;
  ThousandSep: Char;
asm
        PUSH    EDI
        PUSH    ESI
        PUSH    EBX
        MOV     @Result,0
        MOV     ESI,EAX
        MOV     AX,[EDX].TFormatSettings.DecimalSeparator
        MOV     DecimalSep,AX
        MOV     AX,[EDX].TFormatSettings.ThousandSeparator
        MOV     ThousandSep,AX
        MOV     EBX,ECX
        MOV     EAX,ESI
        LEA     EDX,P
        CALL    SysUtils.Trim
        MOV     EAX,P
        OR      EAX,EAX
        JE      @Exit
        MOV     ESI,EAX
        XOR     EAX,EAX
        MOV     InExponent,AL
        MOV     IsNegative,AL
        MOV     IsNegativeExponent,AL
        MOV     Exponent,EAX
        MOV     NumDecimals,EAX
        MOV     DecimalPointPos,EAX
        MOV     EDI,EBX
        MOV     [EBX].Decimal.Lo,EAX
        MOV     [EBX].Decimal.Mid,EAX
        MOV     [EBX].Decimal.Hi,EAX
        MOV     [EBX].Decimal.Flags,EAX

@MainLoop:

        MOV     AX,[ESI].WideChar
        CMP     AX,0
        JE      @EndMainLoop
        CMP     AX,'+'
        JE      @Plus
        CMP     AX,'-'
        JE      @Minus
        CMP     AX,'.'
        JE      @DecimalPoint
        CMP     AX,DecimalSep
        JE      @DecimalPoint
        CMP     AX,ThousandSep
        JE      @Thousands
        CMP     AX,'E'
        JE      @Scientific
        CMP     AX,'e'
        JE      @Scientific
        CMP     AX,'0'
        JL      @EndCase
        CMP     AX,'9'
        JLE     @Digit

@EndCase:

        ADD     ESI,2
        JMP     @MainLoop

@Plus:

        CMP     InExponent,0
        JE      @Pls00
        CMP     ESI,P
        JLE     @Exit
        ADD     ESI,2
        JMP     @MainLoop

@Pls00:

        CMP     ESI,P
        JNE     @Exit
        ADD     ESI,2
        JMP     @MainLoop

@Minus:

        CMP     InExponent,0
        JE      @Min00
        CMP     ESI,P
        JLE     @Exit
        MOV     IsNegativeExponent,1
        ADD     ESI,2
        JMP     @MainLoop

@Min00:

        CMP     ESI,P
        JNE     @Exit
        MOV     IsNegative,1
        ADD     ESI,2
        JMP     @MainLoop

@DecimalPoint:

        CMP     DecimalPointPos,0
        JNE     @Exit
        MOV     DecimalPointPos,ESI

@Thousands:

        ADD     ESI,2
        JMP     @MainLoop

@Scientific:

        MOV     InExponent,1
        CMP     DecimalPointPos,0
        JE      @EndCase
        MOV     EAX,ESI
        SUB     EAX,DecimalPointPos
        SAR     EAX,1
        DEC     EAX
        MOV     NumDecimals,EAX
        MOV     DecimalPointPos,0
        ADD     ESI,2
        JMP     @MainLoop

@Digit:

        SUB     AX,'0'
        MOVZX   EBX,AX
        CMP     InExponent,0
        JE      @Dig00
        MOV     ECX,10
        MOV     EAX,Exponent
        MUL     ECX
        ADD     EAX,EBX
        MOV     Exponent,EAX
        ADD     ESI,2
        JMP     @MainLoop

@Dig00:

        CMP     [EDI].Decimal.Hi,HLWdiv10
        JA      @ExcessDigits
        JB      @NoExcess
        CMP     [EDI].Decimal.Mid,HUI64div10
        JA      @ExcessDigits

@NoExcess:

        MOV     EDX,EBX
        MOV     EAX,EDI
        CALL    DecScaleUpAndAdd
        ADD     ESI,2
        JMP     @MainLoop

@ExcessDigits:

        CMP     EBX,5
        JA      @DoRound
        JB      @NoRound
        TEST    [EDI].Decimal.Lo,1
        JZ      @NoRound

@DoRound:

        ADD     [EDI].Decimal.Lo,1
        ADC     [EDI].Decimal.Mid,0
        ADC     [EDI].Decimal.Hi,0

@NoRound:

        JMP     @EndMainLoop

@OverflowError:

        MOV     EAX,detOverflow
        JMP     Error

@UnderflowError:

        MOV     EAX,detUnderflow
        JMP     Error

@EndMainLoop:

        MOV     EAX,DecimalPointPos
        OR      EAX,EAX
        JE      @M00
        MOV     EDX,ESI
        SUB     EDX,EAX
        SAR     EDX,1
        DEC     EDX
        MOV     NumDecimals,EDX

@M00:

        CMP     IsNegativeExponent,1
        JNE     @M01
        NEG     Exponent

@M01:

        MOV     EAX,NumDecimals
        SUB     EAX,Exponent
        MOV     Exponent,EAX
        CMP     Exponent,-28
        JLE     @OverflowError

@M02:

        CMP     Exponent,28
        JLE     @M03
        MOV     EAX,EDI
        CALL    DecScaleDownRaw
        DEC     Exponent
        JNE     @M02

@M03:

        CMP     Exponent,0
        JGE     @M04
        MOV     EAX,EDI
        CALL    DecScaleUpRaw
        INC     Exponent
        JLE     @M03

@M04:

        CMP     Exponent,0
        JL      @OverflowError
        CMP     IsNegative,1
        JNE     @M05
        OR      [EDI].Decimal.Sign,$80

@M05:

        MOV     EAX,Exponent
        MOV     [EDI].Decimal.Scale,AL
        MOV     @Result,1

@Exit:

        MOVZX   EAX,@Result

        POP     EBX
        POP     ESI
        POP     EDI
end;

class function Decimal.TryParse(const S: string; out Res: Decimal): Boolean;
var
  Settings: TFormatSettings;
begin
  Settings := TFormatSettings.Create;
  Result := TryParse(S, Settings, Res);
end;

procedure InitClassVars;
begin
  Decimal.MaxValue := CMaxValue;
  Decimal.MinValue := CMinValue;
  Decimal.MinusOne := CMinusOne;
  Decimal.Zero := CZero;
  Decimal.One := COne;
  Decimal.OneTenth := COneTenth;
  Decimal.Pi := CPi;
end;

{$IF CompilerVersion >= 21.0}
class constructor Decimal.CreateRecord;
begin
  InitClassVars;
end;
{$ELSE}
initialization
  InitClassVars;
{$IFEND}

end.
